Compare commits

...

142 Commits

Author SHA1 Message Date
bicarus-dev 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
cchike 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-dev 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
cchike 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-dev 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-dev 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
duel0213 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
duel0213 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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
duel0213 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-dev 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
aixxe 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-dev 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-dev 9e2b04d5db troubleshooter: update link for vcredist (#423)
## Description of change
RIP abbodi1406
2025-11-16 00:07:36 -08:00
bicarus-dev 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-dev 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-dev 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-dev 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
wrigglebug 2eca668593 update readme.txt (#415)
please merge this important code
2025-11-02 23:29:48 -08:00
bicarus-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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-sila 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-dev 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
AllanCat 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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
s2 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-dev 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
llm96 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-dev 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-dev 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-dev 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-dev 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-sila 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-dev 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-dev 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-dev 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-dev 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
GEEKiDoS 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
GEEKiDoS 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-dev 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-dev 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-dev 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-dev 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
oleg238948234 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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
Emma 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
sp2xdev 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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
ASleepyCat 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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
sp2xdev e4cfb808bf Update CONTRIBUTING.md 2025-05-04 00:38:10 -07:00
sp2xdev 61d14a16e8 Update CONTRIBUTING.md 2025-05-04 00:33:43 -07:00
bicarus-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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-dev 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
ASleepyCat 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
sp2xdev 0596eeb6fe Update pull_request_template.md 2025-04-19 14:24:21 -07:00
ASleepyCat 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
130 changed files with 4417 additions and 1577 deletions
+5
View File
@@ -13,6 +13,11 @@ jobs:
working-directory: ./src/spice2x
steps:
- uses: actions/checkout@v4
- 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
- name: Calculate commit SHA
id: vars
run: |
+8 -4
View File
@@ -30,12 +30,11 @@ Do not change the names of options, buttons binds, analogs, etc - since they are
* 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 different behavior or loss of functionality.
* 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 submit snippets of code as a "patch". Exceptions can be made for trivial changes (correct a typo, fix a single line of code...), but otherwise, a successfully compiled & fully tested patch file is required when submitting for review.
* 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 debugging post-mortem. At the same time though, avoid spamming the log for something trivial.
* 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.
@@ -44,7 +43,7 @@ Use simple English, avoid colloquialism, and use concise language, even if it's
### Using OS APIs
Avoid making permanent changes to user's OS configuration. For example, spice should 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.
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.
@@ -87,4 +86,9 @@ if (conditional)
}
```
* 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.
+1
View File
@@ -36,6 +36,7 @@ please do not ask for these, as it will never happen here.
Rules for filing a new issue or adding comments to existing issues in the tracker:
* Low effort submissions will be simply deleted, and repeated attempts will get you banned.
* 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.
* 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.
+1 -3
View File
@@ -3,6 +3,7 @@
> * 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.
@@ -12,8 +13,5 @@
## Description of change
*what does this code do?*
## Compiling
*you must be able to compile with the included Docker script with zero compiler warnings*
## Testing
*how was the code tested?*
+1
View File
@@ -2,3 +2,4 @@ bin/**
dist/**
docker/**
cmake-build*
.ccache/**
+8 -1
View File
@@ -16,4 +16,11 @@ build_docker.local.sh
bin/*
dist/*
external/cv2pdb/*
external/cv2pdb/*
.ccache/*
# Visual Studio
.vs
out
CMakeSettings.json
+119 -14
View File
@@ -80,6 +80,25 @@ if(MSVC)
# enable COMDAT folding for even smaller release builds
add_link_options("$<$<CONFIG:RELEASE,MINSIZEREL>:/OPT:ICF>")
# always use UTF-8 (fix 4819)
add_compile_options("/utf-8")
# spectre mitigation warning
add_compile_options("/wd5045")
# implicit type convert warnings
add_compile_options("/wd4244")
add_compile_options("/wd4267")
add_compile_options("/wd4305")
# unreferenced local variable
add_compile_options("/wd4101")
# warning in winbase.h??
add_compile_options("/wd5039")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# disable warnings about using non _s variants like strncpy
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
@@ -124,8 +143,8 @@ else()
# https://github.com/msys2/MINGW-packages/pull/6880
# some games crash if some DLLS load above 4GB VA, so manually set base address to standard 32-bit VA,
# and might as well double make sure ASLR is disabled here
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections,--disable-dynamicbase,--image-base=0x400000")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections,--disable-dynamicbase,--image-base=0x400000")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections,--disable-dynamicbase,--image-base=0x400000,--enable-stdcall-fixup")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections,--disable-dynamicbase,--image-base=0x400000,--enable-stdcall-fixup")
# set visibility to hidden
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -fvisibility=hidden")
@@ -160,8 +179,8 @@ else()
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-fno-ident -ffunction-sections -fdata-sections")
# linker fix to load below 4GB
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections,--disable-dynamicbase,--image-base=0x400000")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections,--disable-dynamicbase,--image-base=0x400000")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections,--disable-dynamicbase,--image-base=0x400000,--enable-stdcall-fixup")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections,--disable-dynamicbase,--image-base=0x400000,--enable-stdcall-fixup")
# set visibility to hidden
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -fvisibility=hidden")
@@ -184,8 +203,8 @@ else()
set(CMAKE_CXX_FLAGS_DEBUG "-gdwarf")
# linker fix to load below 4GB
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--disable-dynamicbase,--image-base=0x400000")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--disable-dynamicbase,--image-base=0x400000")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--disable-dynamicbase,--image-base=0x400000,--enable-stdcall-fixup")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--disable-dynamicbase,--image-base=0x400000,--enable-stdcall-fixup")
# enable debug symbols on level 3 and keep frame pointers
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g3 -fno-omit-frame-pointer")
@@ -338,12 +357,12 @@ set(SOURCE_FILES ${SOURCE_FILES}
external/cardio/cardio_runner.cpp
# external misc
external/scard/scard.cpp
external/stackwalker/stackwalker.cpp
external/tinyxml2/tinyxml2.cpp
external/http-parser/http_parser.c
external/usbhidusage/usb-hid-usage.c
external/toojpeg/toojpeg.cpp
external/scard/scard.cpp
# games
games/game.cpp
@@ -443,6 +462,9 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/mfg/mfg.cpp
games/mfg/io.cpp
games/mfg/bi2a_hook.cpp
games/pc/pc.cpp
games/pc/io.cpp
games/pc/bi2x_hook.cpp
# hooks
hooks/audio/audio.cpp
@@ -588,6 +610,8 @@ set(SOURCE_FILES ${SOURCE_FILES}
util/lz77.cpp
util/tapeled.cpp
util/execexe.cpp
util/dependencies.cpp
util/deferlog.cpp
)
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
@@ -598,8 +622,9 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOUR
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc)
add_executable(spicetools_spice ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_spice
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winscard winhttp
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
target_link_libraries(spicetools_spice PUBLIC winscard)
set_target_properties(spicetools_spice PROPERTIES PREFIX "")
set_target_properties(spicetools_spice PROPERTIES OUTPUT_NAME "spice")
@@ -607,6 +632,39 @@ IF(NOT MSVC)
set_target_properties(spicetools_spice PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# spice_laa.exe
###########
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc)
add_executable(spicetools_spice_laa ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_spice_laa
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
target_link_libraries(spicetools_spice_laa PUBLIC winscard)
set_target_properties(spicetools_spice_laa PROPERTIES PREFIX "")
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)
set_target_properties(spicetools_spice_laa PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32 -Wl,--large-address-aware")
endif()
# spice_linux.exe
###########
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc)
add_executable(spicetools_spice_linux ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_spice_linux
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
set_target_properties(spicetools_spice_linux PROPERTIES PREFIX "")
set_target_properties(spicetools_spice_linux PROPERTIES OUTPUT_NAME "spice_linux")
target_compile_definitions(spicetools_spice_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1)
IF(NOT MSVC)
set_target_properties(spicetools_spice_linux PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# spice64.exe
#############
@@ -615,9 +673,9 @@ add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES})
# do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break
target_link_libraries(spicetools_spice64
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winscard winhttp mfuuid strmiids dxva2
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2
PRIVATE 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 OUTPUT_NAME "spice64")
target_compile_definitions(spicetools_spice64 PRIVATE SPICE64=1)
@@ -626,6 +684,24 @@ IF(NOT MSVC)
set_target_properties(spicetools_spice64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# spice64_linux.exe
#############
set(RESOURCE_FILES build/manifest.manifest build/manifest64.rc build/icon.rc cfg/Win32D.rc)
add_executable(spicetools_spice64_linux ${SOURCE_FILES} ${RESOURCE_FILES})
# do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break
target_link_libraries(spicetools_spice64_linux
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
set_target_properties(spicetools_spice64_linux PROPERTIES PREFIX "")
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 NO_SCARD=1 PRIVATE SPICE_LINUX=1)
IF(NOT MSVC)
set_target_properties(spicetools_spice64_linux PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# spicecfg.exe
##############
@@ -634,8 +710,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)
add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_cfg
PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winscard winhttp strmiids
PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
PRIVATE 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 OUTPUT_NAME "spicecfg")
target_compile_definitions(spicetools_cfg PRIVATE SPICETOOLS_SPICECFG_STANDALONE=1)
@@ -644,6 +721,24 @@ if(NOT MSVC)
set_target_properties(spicetools_cfg PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# spicecfg_linux.exe
##############
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)
add_executable(spicetools_cfg_linux WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_cfg_linux
PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
PRIVATE 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 OUTPUT_NAME "spicecfg_linux")
target_compile_definitions(spicetools_cfg_linux PRIVATE SPICETOOLS_SPICECFG_STANDALONE=1)
target_compile_definitions(spicetools_cfg_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1)
if(NOT MSVC)
set_target_properties(spicetools_cfg_linux PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# stubs
#######
@@ -723,23 +818,33 @@ if(NOT MSVC)
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# cpusbxpkm.dll (32 bit)
set(SOURCE_FILES stubs/cpusbxpkm.cpp)
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} stubs/cpusbxpkm.def)
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES OUTPUT_NAME "cpusbxpkm")
if(NOT MSVC)
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# output directories
####################
# output config
set_target_properties(spicetools_cfg
set_target_properties(spicetools_cfg spicetools_cfg_linux
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools")
# output 32bit
set_target_properties(spicetools_spice spicetools_stubs_kbt spicetools_stubs_kld
set_target_properties(spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_kbt spicetools_stubs_kld spicetools_stubs_cpusbxpkm
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive32"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32")
# output 64bit
set_target_properties(spicetools_spice64 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
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive64"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64"
+1 -1
View File
@@ -4,4 +4,4 @@ WORKDIR /src
COPY --from=gitroot . /src/.git
COPY . /src/src/spice2x
WORKDIR /src/src/spice2x
CMD ["./build_all.sh"]
ENTRYPOINT ["./build_all.sh"]
+19
View File
@@ -247,6 +247,25 @@ which also means that your hex edits are applicable directly.
- 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()
+4
View File
@@ -41,6 +41,7 @@
namespace acio {
HINSTANCE DLL_INSTANCE = nullptr;
std::vector<acio::ACIOModule *> MODULES;
std::atomic<bool> IO_INIT_IN_PROGRESS = false;
}
/*
@@ -58,6 +59,7 @@ static inline acio::HookMode get_hookmode() {
void acio::attach() {
log_info("acio", "SpiceTools ACIO");
IO_INIT_IN_PROGRESS = true;
// load settings and instance
acio::DLL_INSTANCE = LoadLibraryA("libacio.dll");
@@ -111,6 +113,8 @@ void acio::attach() {
for (auto &module : MODULES) {
module->attach();
}
IO_INIT_IN_PROGRESS = false;
}
void acio::attach_icca() {
+2
View File
@@ -1,5 +1,6 @@
#pragma once
#include <atomic>
#include <vector>
#include <windows.h>
@@ -11,6 +12,7 @@ namespace acio {
// globals
extern HINSTANCE DLL_INSTANCE;
extern std::vector<acio::ACIOModule *> MODULES;
extern std::atomic<bool> IO_INIT_IN_PROGRESS;
void attach();
void attach_icca();
+46 -23
View File
@@ -11,13 +11,14 @@
const size_t STATUS_BUFFER_SIZE = 32;
// static stuff
static uint8_t COUNTER = 0;
static uint8_t HEAD_P1 = 0;
static uint8_t HEAD_P2 = 0;
// buffers
#pragma pack(push, 1)
static struct {
uint8_t STATUS_BUFFER_P1[7][STATUS_BUFFER_SIZE] {};
uint8_t STATUS_BUFFER_P2[7][STATUS_BUFFER_SIZE] {};
uint8_t STATUS_BUFFER_P1[16][STATUS_BUFFER_SIZE] {};
uint8_t STATUS_BUFFER_P2[16][STATUS_BUFFER_SIZE] {};
} BUFFERS {};
#pragma pack(pop)
@@ -40,29 +41,50 @@ static uint64_t arkGetTickTime64() {
* Implementations
*/
static bool __cdecl ac_io_mdxf_get_control_status_buffer(int node, void *buffer, uint8_t a3, uint8_t a4) {
static uint64_t __cdecl ac_io_mdxf_get_control_status_buffer(int node, void *out, uint8_t index, uint8_t head_in) {
// Default error value (matches original mask behavior)
auto error_ret = static_cast<uint64_t>(node - 0x11) & 0xFFFFFFFFFFFFFF00;
// Dance Dance Revolution
if (avs::game::is_model("MDX")) {
// get buffer index
auto i = (COUNTER + a3) % std::size(BUFFERS.STATUS_BUFFER_P1);
// Select player-specific state
uint8_t head;
uint8_t (*buffer)[STATUS_BUFFER_SIZE];
size_t size;
if (node == 17 || node == 25) {
head = HEAD_P1;
buffer = BUFFERS.STATUS_BUFFER_P1;
size = std::size(BUFFERS.STATUS_BUFFER_P1);
} else if (node == 18 || node == 26) {
head = HEAD_P2;
buffer = BUFFERS.STATUS_BUFFER_P2;
size = std::size(BUFFERS.STATUS_BUFFER_P2);
} else {
memset(out, 0, STATUS_BUFFER_SIZE);
return error_ret;
}
// copy buffer
if (node == 17 || node == 25) {
memcpy(buffer, BUFFERS.STATUS_BUFFER_P1[i], STATUS_BUFFER_SIZE);
} else if (node == 18 || node == 26) {
memcpy(buffer, BUFFERS.STATUS_BUFFER_P2[i], STATUS_BUFFER_SIZE);
} else {
if (head_in != 0xFF) {
head = head_in;
}
// fill with zeros on unknown node
memset(buffer, 0, STATUS_BUFFER_SIZE);
return false;
}
// Compute ring index: walk backwards from head as index increases
// Assumes ring buffer size is a power of two
const size_t mask = size - 1;
const size_t offset = static_cast<size_t>(index) & mask;
const size_t i = (static_cast<size_t>(head) - offset + size) & mask;
// Copy the chosen entry
memcpy(out, buffer[i], STATUS_BUFFER_SIZE);
// Return the head value actually used
return static_cast<uint64_t>(head);
}
// return success
return true;
return error_ret;
}
static bool __cdecl ac_io_mdxf_set_output_level(unsigned int a1, unsigned int a2, uint8_t value) {
@@ -106,9 +128,6 @@ static bool __cdecl ac_io_mdxf_set_output_level(unsigned int a1, unsigned int a2
static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) {
// increase counter
COUNTER = (COUNTER + 1) % std::size(BUFFERS.STATUS_BUFFER_P1);
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
@@ -119,11 +138,15 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) {
switch (node) {
case 17:
case 25:
buffer = BUFFERS.STATUS_BUFFER_P1[COUNTER];
// increase counter
HEAD_P1 = (HEAD_P1 + 1) % std::size(BUFFERS.STATUS_BUFFER_P1);
buffer = BUFFERS.STATUS_BUFFER_P1[HEAD_P1];
break;
case 18:
case 26:
buffer = BUFFERS.STATUS_BUFFER_P2[COUNTER];
// increase counter
HEAD_P2 = (HEAD_P2 + 1) % std::size(BUFFERS.STATUS_BUFFER_P2);
buffer = BUFFERS.STATUS_BUFFER_P2[HEAD_P2];
break;
default:
+1 -1
View File
@@ -31,7 +31,7 @@ namespace acio2emu {
}
};
IOBHandle::IOBHandle(std::wstring device) : device_(device) {
IOBHandle::IOBHandle(LPCWSTR device) : device_(device) {
nodes_[0] = std::make_unique<MasterNode>(this);
}
+1 -1
View File
@@ -26,7 +26,7 @@ namespace acio2emu {
void forward_packet_(const Packet &packet);
public:
IOBHandle(std::wstring device);
IOBHandle(LPCWSTR device);
bool register_node(std::unique_ptr<Node> node);
int number_of_nodes() const;
+3 -3
View File
@@ -102,7 +102,7 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
// send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
if (
avs::game::is_model({"LDJ", "TBS", "UJK"}) ||
avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
// SDVX Valkyrie cabinet mode
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))
) {
@@ -373,7 +373,7 @@ void ICCADevice::update_card(int unit) {
bool kb_insert_press = false;
// eamio keypress
kb_insert_press |= eamuse_get_keypad_state((size_t) unit) & (1 << EAM_IO_INSERT);
kb_insert_press |= static_cast<bool>(eamuse_get_keypad_state((size_t) unit) & (1 << EAM_IO_INSERT));
// check for card
if (this->cards[unit] == nullptr && (eamuse_card_insert_consume(this->node_count, unit) || kb_insert_press)) {
@@ -521,7 +521,7 @@ void ICCADevice::update_status(int unit) {
buffer[10] = felica ? 0x01 : 0x00;
} else if (
avs::game::is_model({"LDJ", "TBS"}) ||
avs::game::is_model({"LDJ", "TBS", "XIF"}) ||
// SDVX Valkyrie cabinet mode
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))) {
+15 -3
View File
@@ -9,6 +9,9 @@ using namespace rapidjson;
namespace api::modules {
std::optional<uint32_t> CAPTURE_QUALITY;
std::optional<uint32_t> CAPTURE_DIVIDE;
static thread_local std::vector<uint8_t> CAPTURE_BUFFER;
Capture::Capture() : Module("capture") {
@@ -44,12 +47,21 @@ namespace api::modules {
int screen = 0;
int quality = 70;
int divide = 1;
if (req.params.Size() > 0 && req.params[0].IsUint())
if (req.params.Size() > 0 && req.params[0].IsUint()) {
screen = req.params[0].GetUint();
if (req.params.Size() > 1 && req.params[1].IsUint())
}
if (CAPTURE_QUALITY.has_value()) {
quality = CAPTURE_QUALITY.value();
} else if (req.params.Size() > 1 && req.params[1].IsUint()) {
quality = req.params[1].GetUint();
if (req.params.Size() > 2 && req.params[2].IsUint())
}
if (CAPTURE_DIVIDE.has_value()) {
divide = CAPTURE_DIVIDE.value();
} else if (req.params.Size() > 2 && req.params[2].IsUint()) {
divide = req.params[2].GetUint();
}
// receive JPEG data
uint64_t timestamp = 0;
+5
View File
@@ -1,10 +1,15 @@
#pragma once
#include <optional>
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
extern std::optional<uint32_t> CAPTURE_QUALITY;
extern std::optional<uint32_t> CAPTURE_DIVIDE;
class Capture : public Module {
public:
Capture();
+52 -4
View File
@@ -1,13 +1,9 @@
#include "iidx.h"
#include <functional>
#include <vector>
#include "games/iidx/iidx.h"
#include "external/rapidjson/document.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
// settings
@@ -17,6 +13,11 @@ namespace api::modules {
functions["ticker_get"] = std::bind(&IIDX::ticker_get, this, _1, _2);
functions["ticker_set"] = std::bind(&IIDX::ticker_set, this, _1, _2);
functions["ticker_reset"] = std::bind(&IIDX::ticker_reset, this, _1, _2);
functions["tapeled_get"] = std::bind(&IIDX::tapeled_get, this, _1, _2);
for (auto &light : games::iidx::TAPELED_MAPPING) {
this->lights_by_names.emplace(light.lightName, light);
}
}
/**
@@ -69,4 +70,51 @@ namespace api::modules {
// disable read only
games::iidx::IIDXIO_LED_TICKER_READONLY = false;
}
/**
* tapeled_get()
* tapeled_get(name: str, ...)
*/
void IIDX::tapeled_get(Request &req, Response &res) {
Value response_object(kObjectType);
// all tape leds
if (req.params.Size() == 0) {
// Iterate through each device and dump its lights data into the response
for (const auto &mapping : games::iidx::TAPELED_MAPPING) {
copy_tapeled_data(res, response_object, mapping);
}
} else {
// specified light names
for (Value &param : req.params.GetArray()) {
// check params
if (!param.IsString()) {
error_type(res, "name", "string");
return;
}
const auto name = param.GetString();
if (const auto &it = lights_by_names.find(name); it != lights_by_names.end()) {
const auto mapping = it->second.get();
copy_tapeled_data(res, response_object, mapping);
}
}
}
res.add_data(response_object);
}
void IIDX::copy_tapeled_data(Response &res, Value &response_object, const tapeledutils::tape_led &mapping) {
// Create an array for the light state
Value light_state(kArrayType);
light_state.Reserve(mapping.data.capacity() * 3, res.doc()->GetAllocator());
for (const auto [r, g, b] : mapping.data) {
light_state.PushBack(r, res.doc()->GetAllocator());
light_state.PushBack(g, res.doc()->GetAllocator());
light_state.PushBack(b, res.doc()->GetAllocator());
}
// Can't use StringRef here, turns some strings partially into null bytes for some reason
Value light_name(mapping.lightName.c_str(), res.doc()->GetAllocator());
response_object.AddMember(light_name, light_state, res.doc()->GetAllocator());
}
}
+7
View File
@@ -2,6 +2,7 @@
#include "api/module.h"
#include "api/request.h"
#include "games/iidx/iidx.h"
namespace api::modules {
@@ -10,10 +11,16 @@ namespace api::modules {
IIDX();
private:
// state
robin_hood::unordered_map<std::string, std::reference_wrapper<tapeledutils::tape_led>> lights_by_names;
// function definitions
void ticker_get(Request &req, Response &res);
void ticker_set(Request &req, Response &res);
void ticker_reset(Request &req, Response &res);
void tapeled_get(Request &req, Response &res);
// helper
void copy_tapeled_data(Response &res, rapidjson::Value &response_object, const tapeledutils::tape_led &mapping);
};
}
@@ -16,3 +16,13 @@ def iidx_ticker_set(con: Connection, text: str):
def iidx_ticker_reset(con: Connection):
req = Request("iidx", "ticker_reset")
con.request(req)
def iidx_tapeled_get(con: Connection, *light_names):
req = Request("iidx", "tapeled_get")
for light_name in light_names:
req.add_param(light_name)
res = con.request(req)
return res.get_data()
+40 -7
View File
@@ -8,6 +8,8 @@
#include "external/robin_hood.h"
#include "launcher/logger.h"
#include "launcher/signal.h"
#include "util/deferlog.h"
#include "util/detour.h"
#include "util/fileutils.h"
#include "util/libutils.h"
@@ -1501,7 +1503,8 @@ namespace avs {
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
"\n"
, DLL_NAME, MODULE_PATH.string()) };
log_fatal("avs-ea3", "{}", info_str);
log_warning("avs-ea3", "{}", info_str);
log_fatal("avs-ea3", "Failed to find critical avs DLL on disk (avs2-core.dll OR {})", DLL_NAME);
}
}
@@ -1736,7 +1739,17 @@ namespace avs {
return;
}
static void create_dir(
static void avs_dir_err(const std::filesystem::path &src_path)
{
deferredlogs::defer_error_messages({
"AVS filesystem initialization failure was previously detected during boot!",
fmt::format(" ERROR: directory could not be created: {}", src_path.string().c_str()),
fmt::format(" if you see a crash, it may have been caused by bad <mounttable> contents in {}", avs::core::CFG_PATH.c_str()),
" fix the XML file and try again"
});
}
static void create_avs_dir(
const std::string_view &avs_path,
const std::string_view &src_path)
{
@@ -1745,6 +1758,7 @@ namespace avs {
auto real_path = std::filesystem::absolute(src_path, err);
if (err) {
avs_dir_err(real_path);
log_warning("avs-core", "failed to resolve '{}' path: {}", avs_path, err.message());
return;
}
@@ -1754,7 +1768,9 @@ namespace avs {
if (created) {
log_info("avs-core", "created '{}' at '{}'", avs_path, real_path.string());
}
if (err) {
avs_dir_err(real_path);
log_warning("avs-core", "failed to create '{}' folder at '{}': {}",
avs_path,
real_path.string(),
@@ -1815,7 +1831,7 @@ namespace avs {
return;
}
create_dir(avs_path, device_path);
create_avs_dir(avs_path, device_path);
}
static void create_avs_config_fs_table(
@@ -1870,7 +1886,7 @@ namespace avs {
continue;
}
create_dir(dst_path, src_path);
create_avs_dir(dst_path, src_path);
} while ((vfs_node = property_node_traversal(vfs_node, TRAVERSE_NEXT_SEARCH_RESULT)));
}
@@ -1924,6 +1940,7 @@ namespace avs {
static const char *LOG_LEVELS[] = { "disable", "fatal", "warning", "info", "misc", "all" };
char current_log_level_buffer[32] { 0 };
uint32_t current_log_level = 0;
std::string log_level_as_str;
if (VERSION == AVSLEGACY || VERSION == AVS21360) {
auto level_node = property_search(config, config_node, "/log/level");
@@ -1939,6 +1956,7 @@ namespace avs {
{
if (current_log_level < std::size(LOG_LEVELS)) {
log_info("avs-core", "log level: {}", LOG_LEVELS[current_log_level]);
log_level_as_str = LOG_LEVELS[current_log_level];
} else {
log_fatal("avs-core", "log level ({}) is invalid!", current_log_level);
}
@@ -1958,7 +1976,7 @@ namespace avs {
level_node = property_node_create(config, config_node, NODE_TYPE_str, "/log/level",
LOG_LEVELS[current_log_level]);
} else {
log_fatal("avs-core", "log level ({}) is invalid!", current_log_level);
log_fatal("avs-core", "log level ({}) is invalid! *", current_log_level);
}
}
@@ -1971,12 +1989,27 @@ namespace avs {
if (property_node_refer(config, config_node, "/log/level",
NODE_TYPE_str, current_log_level_buffer, sizeof(current_log_level_buffer)) > 0)
{
log_info("avs-core", "log level: {}", current_log_level_buffer);
log_info("avs-core", "log level: {} *", current_log_level_buffer);
log_level_as_str = current_log_level_buffer;
} else {
log_warning("avs-core", "log level: unknown");
log_warning("avs-core", "log level: unknown *");
}
}
if (log_level_as_str == "disable" ||
log_level_as_str == "fatal" ||
log_level_as_str == "warning" ||
log_level_as_str == "info") {
deferredlogs::defer_error_messages({
fmt::format(
"log level is set to `{}` (either in avs-config.xml or using -loglevel)", log_level_as_str),
" 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`",
});
}
// fix time offset
auto t_now = std::time(nullptr);
auto tm_now = *std::gmtime(&t_now);
+1
View File
@@ -4,6 +4,7 @@
#include <cstddef>
#include <string>
#include <windows.h>
#include <sys/stat.h>
namespace avs {
namespace core {
+35 -5
View File
@@ -8,6 +8,7 @@
#include "games/mfc/mfc.h"
#include "hooks/avshook.h"
#include "util/detour.h"
#include "util/deferlog.h"
#include "util/fileutils.h"
#include "util/libutils.h"
#include "util/logging.h"
@@ -41,6 +42,7 @@ namespace avs {
int HTTP11 = -1;
int URL_SLASH = -1;
int PCB_TYPE = -1;
bool EA3_DEBUG_VERBOSE = false;
// handle
std::string VERSION_STR = "unknown";
@@ -149,7 +151,9 @@ namespace avs {
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
"\n"
, DLL_NAME, MODULE_PATH.string()) };
log_fatal("avs-ea3", "{}", info_str);
log_warning("avs-ea3", "{}", info_str);
log_fatal("avs-ea3", "Failed to find critical ea3 DLL on disk (avs2-ea3.dll OR {})", DLL_NAME);
}
}
@@ -279,6 +283,7 @@ namespace avs {
// read software nodes
if (ea3_soft != nullptr) {
log_misc("avs-ea3", "reading soft id from {}...", ea3_config_name);
avs::core::property_node_refer(ea3_config, ea3_soft, "model",
avs::core::NODE_TYPE_str, EA3_MODEL, 4);
avs::core::property_node_refer(ea3_config, ea3_soft, "dest",
@@ -290,6 +295,7 @@ namespace avs {
avs::core::property_node_refer(ea3_config, ea3_soft, "ext",
avs::core::NODE_TYPE_str, EA3_EXT, 11);
} else if (fileutils::file_exists("prop/ea3-ident.xml")) {
log_misc("avs-ea3", "reading soft id from prop/ea3-ident.xml...");
// read ident config
auto ea3_ident = avs::core::config_read("prop/ea3-ident.xml");
@@ -313,7 +319,14 @@ namespace avs {
// clean up
avs::core::config_destroy(ea3_ident);
} else {
log_fatal("avs-ea3", "node not found in '{}': /ea3/soft", ea3_config_name);
// <ea3><soft> node missing error
log_warning("avs-ea3", "soft id (datecode) not found in prop XML files");
log_warning("avs-ea3", "get fixed prop files and try again - you have incomplete data");
log_warning("avs-ea3", "for experts only:");
log_warning("avs-ea3", " * add the missing <soft>...</soft> node to {}", ea3_config_name);
log_warning("avs-ea3", " * alternatively, provide ea3-ident.xml with <ea3_conf><soft>...");
log_fatal("avs-ea3", "datecode missing in '{}'; 'prop/ea3-ident.xml' also missing", ea3_config_name);
}
// set account id (`EA3_PCBID` is valid if and only if `/ea3/id` is present)
@@ -356,6 +369,12 @@ namespace avs {
}
}
// fall back to default PCBID if node is not found
if (!EA3_PCBID[0] && PCBID_CUSTOM.empty()) {
log_warning("avs-ea3", "no PCBID set, falling back to default PCBID value (04040000000000000000)");
PCBID_CUSTOM = "04040000000000000000";
}
// custom PCBID
if (!PCBID_CUSTOM.empty()) {
@@ -399,13 +418,18 @@ namespace avs {
avs::core::NODE_TYPE_str, "/ea3/id/accountid", &EA3_ACCOUNTID);
}
// check if PCBID is defined
// check if PCBID is defined - should never hit, left for sanity check only
if (avs::core::property_search(ea3_config, nullptr, "/ea3/id/pcbid") == nullptr) {
log_fatal("avs-ea3", "node not found: /ea3/id/pcbid (try using -p to specify PCBID)");
} else if (strlen(EA3_PCBID) == 0) {
log_fatal("avs-ea3", "no PCBID set (try using -p to specify PCBID)");
}
// remove <debug> since it can expose pcbid in logged requests
if (!EA3_DEBUG_VERBOSE) {
avs::core::property_search_remove_safe(ea3_config, nullptr, "/ea3/debug/verbose");
}
// remember IDs
char pcbid_buffer[256] { 0 };
char accountid_buffer[256] { 0 };
@@ -470,8 +494,13 @@ namespace avs {
std::ostringstream init_code;
init_code << EA3_MODEL;
init_code << EA3_DEST;
init_code << EA3_SPEC;
init_code << EA3_REV;
if (strcmp(EA3_MODEL, "MDX") == 0 && strcmp(EA3_SPEC, "I") == 0) {
init_code << "G";
init_code << "B";
} else {
init_code << EA3_SPEC;
init_code << EA3_REV;
}
init_code << EA3_EXT;
std::string init_code_str = init_code.str();
@@ -549,6 +578,7 @@ namespace avs {
soft_id_code << EA3_EXT;
std::string soft_id_code_str = soft_id_code.str();
log_info("avs-ea3", "soft id code: {}", soft_id_code_str);
deferredlogs::set_softid(soft_id_code_str);
// set soft ID code
avs::core::avs_std_setenv("/env/profile/soft_id_code", soft_id_code_str.c_str());
+1
View File
@@ -28,6 +28,7 @@ namespace avs {
extern int HTTP11;
extern int URL_SLASH;
extern int PCB_TYPE;
extern bool EA3_DEBUG_VERBOSE;
// handle
extern std::string VERSION_STR;
+30 -8
View File
@@ -76,24 +76,46 @@ namespace avs {
const auto dll_path_s = dll_path.string();
log_info("avs-game", "DLL path: {}", dll_path_s.c_str());
if (MAX_PATH <= (dll_path_s.length() + 64)) {
// MAX_PATH is 260
if (130 <= dll_path_s.length()) {
log_warning(
"avs-game",
"PATH TOO LONG WARNING\n\n\n"
"PATH TOO LONG WARNING\n\n"
"-------------------------------------------------------------------\n"
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
"WARNING - WARNING - WARNING - WARNING - WARNING - WARNING - WARNING\n"
" PATH TOO LONG \n"
"WARNING - WARNING - WARNING - WARNING - WARNING - WARNING - WARNING\n"
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
"'{}' has a length of {}\n"
"Most of these games will behave unexpectedly when the path is too long,\n"
"often resulting in random crashes. Move the game contents to a directory\n"
"with shorter path.\n"
"-------------------------------------------------------------------\n\n\n",
"The path '{}'\n"
" has a length of {}\n"
"Most of these games may behave unexpectedly when the path is too\n"
"long, often resulting in random crashes. Move the game contents to\n"
"a directory with shorter path.\n"
"-------------------------------------------------------------------\n\n",
dll_path_s, dll_path_s.length());
}
// ddr gamemdx.dll user error
if (avs::game::is_model("MDX") && DLL_NAME == "gamemdx.dll") {
log_warning(
"ddr",
"BAD GAME DLL ERROR\n\n"
"!!! !!!\n"
"!!! -exec gamemdx.dll was specified !!!\n"
"!!! this is the wrong DLL; the game will not load !!!\n"
"!!! remove -exec argument and try again. !!!\n"
"!!! !!!\n"
);
log_fatal("ddr", "BAD GAME DLL ERROR (don't use -exec gamemdx.dll, that's the wrong DLL)");
}
// file not found on disk
if (!fileutils::file_exists(dll_path)) {
log_warning("avs-game", "game DLL could not be found on disk: {}", dll_path.string().c_str());
log_warning("avs-game", "double check -exec and -modules parameters; unless you know what you're doing, leave them blank");
}
if (fileutils::verify_header_pe(dll_path)) {
DLL_INSTANCE = libutils::load_library(dll_path);
}
@@ -101,7 +123,7 @@ namespace avs {
// load entry points
dll_entry_init = (ENTRY_INIT_T) libutils::get_proc(DLL_INSTANCE, ENTRY_INIT_NAME);
dll_entry_main = (ENTRY_MAIN_T) libutils::get_proc(DLL_INSTANCE, ENTRY_MAIN_NAME);
log_info("avs-game", "loaded successfully ({})", fmt::ptr(DLL_INSTANCE));
log_info("avs-game", "loaded {} successfully ({})", DLL_NAME, fmt::ptr(DLL_INSTANCE));
}
bool entry_init(char *sid_code, void *app_param) {
-1
View File
@@ -2,7 +2,6 @@
#define IDC_STATIC -1
#define IDD_DIALOG1 129
#define IDR_CHANGELOG 130
#define IDR_LICENSES 131
#define IDR_PATCHES 132
#define IDR_README 133
+52 -7
View File
@@ -19,6 +19,25 @@ function trap_error_exit {
trap trap_error_dbg DEBUG
trap trap_error_exit EXIT
IGNORE_CACHE=0
# Parse options
while getopts "ih" opt; do
case $opt in
i) IGNORE_CACHE=1 ;;
h)
echo "Usage: $0 [-i]"
echo " -i: Ignore build cache"
echo " -h: Show this help"
exit 0
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
esac
done
# settings
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
GIT_HEAD=$(git rev-parse HEAD || echo "none")
@@ -30,6 +49,7 @@ BUILDDIR_64_RELEASE="./cmake-build-release-64"
BUILDDIR_64_DEBUG="./cmake-build-debug-64"
DEBUG=0
OUTDIR="./bin/spice2x"
OUTDIR_EXTRAS="./bin/spice2x/extras"
# disabled UPX since it tends to falsely trigger malware detection
UPX_ENABLE=0
@@ -40,9 +60,10 @@ INCLUDE_SRC=1
DIST_ENABLE=1
DIST_FOLDER="./dist"
DIST_NAME="spice2x-$(date +%y)-$(date +%m)-$(date +%d).zip"
DIST_NAME_EXTRAS="spice2x-$(date +%y)-$(date +%m)-$(date +%d)-full.zip"
DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.'
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_spice"
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64"
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm"
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux"
# determine build type
BUILD_TYPE="Release"
@@ -74,6 +95,15 @@ echo "Build Type: $BUILD_TYPE"
echo "Cores: $CORES"
echo ""
if ((IGNORE_CACHE > 0))
then
echo "Ignoring build cache..."
else
export CCACHE_DIR="$(pwd)/.ccache"
export CMAKE_CXX_COMPILER_LAUNCHER=ccache
export CMAKE_C_COMPILER_LAUNCHER=ccache
fi
time (
# 32 bit
echo "Building 32bit targets..."
@@ -151,10 +181,17 @@ fi
# copy to output directory
echo "Copy files to output directory..."
rm -rf ${OUTDIR}
mkdir -p ${OUTDIR}
#mkdir -p ${OUTDIR}/stubs/32
mkdir -p ${OUTDIR}/stubs/32
mkdir -p ${OUTDIR}/stubs/64
rm -rf ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware
mkdir -p ${OUTDIR_EXTRAS}/linux
if false # ((DEBUG > 0))
then
# debug files
@@ -171,18 +208,24 @@ then
#cp ${BUILDDIR_64}/spicetools/64/nvEncodeAPI64.dll ${OUTDIR}/stubs/64 2>/dev/null
#cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
#cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
#cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
else
# release files
cp ${BUILDDIR_32}/spicetools/spicecfg.exe ${OUTDIR} 2>/dev/null
cp ${BUILDDIR_32}/spicetools/spicecfg_linux.exe ${OUTDIR_EXTRAS}/linux/spicecfg.exe 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/spice.exe ${OUTDIR} 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/spice_laa.exe ${OUTDIR_EXTRAS}/largeaddressaware/spice.exe 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/spice_linux.exe ${OUTDIR_EXTRAS}/linux/spice.exe 2>/dev/null
#cp ${BUILDDIR_32}/spicetools/32/kbt.dll ${OUTDIR}/stubs/32 2>/dev/null
#cp ${BUILDDIR_32}/spicetools/32/kld.dll ${OUTDIR}/stubs/32 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/spice64.exe ${OUTDIR} 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/spice64_linux.exe ${OUTDIR_EXTRAS}/linux/spice64.exe 2>/dev/null
#cp ${BUILDDIR_64}/spicetools/64/kbt.dll ${OUTDIR}/stubs/64 2>/dev/null
#cp ${BUILDDIR_64}/spicetools/64/kld.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/nvEncodeAPI64.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
fi
# pack source files to output directory
@@ -196,10 +239,10 @@ then
fi
# copy resources
rm -rf ${OUTDIR}/api
mkdir ${OUTDIR}/api
rm -rf ${OUTDIR_EXTRAS}/api
mkdir ${OUTDIR_EXTRAS}/api
find ./api/resources/python -name "__pycache__" -exec rm -rf {} +
cp -r ./api/resources/* ${OUTDIR}/api
cp -r ./api/resources/* ${OUTDIR_EXTRAS}/api
# build distribution archive
if ((DIST_ENABLE > 0))
@@ -208,7 +251,9 @@ then
mkdir -p ${DIST_FOLDER}
rm -rf ${DIST_FOLDER}/${DIST_NAME}
pushd ${OUTDIR}/.. > /dev/null
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME} . -z <<< "$DIST_COMMENT"
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME} spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT"
echo "Building extras..."
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS} spice2x -z <<< "$DIST_COMMENT"
popd > /dev/null
fi
+2 -2
View File
@@ -1,6 +1,6 @@
docker build --pull external/docker -t spicetools/deps
docker build --build-context gitroot=%cd%/../../.git . -t spicetools/spice --no-cache
docker run --rm -it -v %cd%/dist:/src/src/spice2x/dist -v %cd%/bin:/src/src/spice2x/bin spicetools/spice
docker build --build-context gitroot=%cd%/../../.git . -t spicetools/spice
docker run --rm -it -v %cd%/dist:/src/src/spice2x/dist -v %cd%/bin:/src/src/spice2x/bin -v %cd%/.ccache:/src/src/spice2x/.ccache spicetools/spice %*
@REM to generate PDBs, set DEBUG to 1 in build_all.sh, place cv2pdb in external\cv2pdb, and run below
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spicecfg.exe bin\spice2x\spicecfg-pdb.exe bin\spice2x\spicecfg-pdb.pdb
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spice.exe bin\spice2x\spice-pdb.exe bin\spice2x\spice-pdb.pdb
+2 -2
View File
@@ -1,4 +1,4 @@
#!/bin/bash
docker build --pull $PWD/external/docker -t spicetools/deps --platform linux/x86_64
docker build --build-context gitroot=$PWD/../../.git . -t spicetools/spice:latest --no-cache
docker run --rm -v $PWD/dist:/src/src/spice2x/dist -v $PWD/bin:/src/src/spice2x/bin spicetools/spice
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 "$@"
+3
View File
@@ -0,0 +1,3 @@
del /s /q .ccache
del /s /q dist
call build_docker.bat
-1
View File
@@ -9,7 +9,6 @@
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
IDR_CHANGELOG RCDATA "../changelog.txt"
IDR_LICENSES RCDATA "../licenses.txt"
IDR_PATCHES RCDATA "../build/patches.json"
IDR_README RCDATA "../readme.txt"
+8 -3
View File
@@ -82,18 +82,23 @@ public:
return this->index != 0xFF;
}
inline void clearBindings() {
device_identifier = "";
index = 0xFF;
inline void resetValues() {
setSensitivity(1.f);
setDeadzone(0.f);
invert = false;
smoothing = false;
deadzone_mirror = false;
setMultiplier(1);
setRelativeMode(false);
setDelayBufferDepth(0);
}
inline void clearBindings() {
device_identifier = "";
index = 0xFF;
resetValues();
}
inline const std::string &getName() const {
return this->name;
}
+34 -4
View File
@@ -1,5 +1,8 @@
#include "api.h"
#include <optional>
#include "launcher/superexit.h"
#include "rawinput/rawinput.h"
#include "rawinput/piuio.h"
#include "util/time.h"
@@ -64,18 +67,29 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
auto current_button = &_button;
auto alternatives = check_alts ? &current_button->getAlternatives() : nullptr;
unsigned int button_count = 0;
std::optional<bool> window_has_focus;
while (true) {
// naive behavior
if (current_button->isNaive()) {
GameAPI::Buttons::State state;
auto vkey = current_button->getVKey();
// check for focus
if (vkey != INVALID_VKEY && rawinput::NAIVE_REQUIRE_FOCUS) {
if (!window_has_focus.has_value()) {
window_has_focus = superexit::has_focus();
}
if (!window_has_focus.value()) {
vkey = INVALID_VKEY;
}
}
// read
auto vkey = current_button->getVKey();
GameAPI::Buttons::State state;
if (vkey == 0xFF) {
if (vkey == INVALID_VKEY) {
state = BUTTON_NOT_PRESSED;
} else {
state = (GetAsyncKeyState(current_button->getVKey()) & 0x8000) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
state = (GetAsyncKeyState(vkey) & 0x8000) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
}
// invert
@@ -106,6 +120,16 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
auto &devid = current_button->getDeviceIdentifier();
auto device = manager->devices_get(devid, false); // TODO: fix to update only
// check for focus
if (device && rawinput::RAWINPUT_REQUIRE_FOCUS) {
if (!window_has_focus.has_value()) {
window_has_focus = superexit::has_focus();
}
if (!window_has_focus.value()) {
device = nullptr;
}
}
// get state if device was marked as updated
GameAPI::Buttons::State state = current_button->getLastState();
double *last_up = nullptr;
@@ -571,6 +595,12 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
switch (device->type) {
case rawinput::MOUSE: {
// check for focus
if (rawinput::NAIVE_REQUIRE_FOCUS && !superexit::has_focus()) {
value = analog.getLastState();
break;
}
// get mouse position
auto mouse = device->mouseInfo;
long pos;
+1 -1
View File
@@ -284,7 +284,7 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
std::string vKeyString = fmt::format("{:#x}", vKey);
// device must be existing
if (this->device_identifier.empty() && vKey == 0xFF) {
if (this->device_identifier.empty() && vKey == INVALID_VKEY) {
return "";
}
+6 -4
View File
@@ -33,12 +33,14 @@ enum ButtonAnalogType {
extern const char *ButtonAnalogTypeStr[];
constexpr unsigned short INVALID_VKEY = UINT16_C(0xFF);
class Button {
private:
std::vector<Button> alternatives;
std::string name;
std::string device_identifier = "";
unsigned short vKey = 0xFF;
unsigned short vKey = INVALID_VKEY;
ButtonAnalogType analog_type = BAT_NONE;
double debounce_up = 0.0;
double debounce_down = 0.0;
@@ -68,12 +70,12 @@ public:
if (this->override_enabled) {
return true;
}
if (this->vKey != 0xFF) {
if (this->vKey != INVALID_VKEY) {
return true;
}
for (auto &alternative : this->alternatives) {
if (alternative.vKey != 0xFF) {
if (alternative.vKey != INVALID_VKEY) {
return true;
}
}
@@ -82,7 +84,7 @@ public:
}
inline void clearBindings() {
vKey = 0xFF;
vKey = INVALID_VKEY;
alternatives.clear();
device_identifier = "";
analog_type = BAT_NONE;
+1
View File
@@ -43,6 +43,7 @@ public:
std::string value;
std::vector<Option> alternatives;
bool disabled = false;
bool conflicting = false;
explicit Option(OptionDefinition definition, std::string value = "") :
definition(std::move(definition)), value(std::move(value)) {
-1
View File
@@ -1,6 +1,5 @@
#pragma once
#define IDR_CHANGELOG 130
#define IDR_LICENSES 131
#define IDR_PATCHES 132
#define IDR_README 133
+15 -12
View File
@@ -15,12 +15,18 @@ namespace cfg {
std::optional<std::string> SCREEN_RESIZE_CFG_PATH_OVERRIDE;
ScreenResize::ScreenResize() {
bool file_exists = false;
if (SCREEN_RESIZE_CFG_PATH_OVERRIDE.has_value()) {
this->config_path = SCREEN_RESIZE_CFG_PATH_OVERRIDE.value();
if (fileutils::file_exists(this->config_path)) {
log_info("ScreenResize", "loading config from: {}", this->config_path.string());
file_exists = true;
}
} else {
this->config_path = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools_screen_resize.json";
this->config_path =
fileutils::get_config_file_path("ScreenResize", "spicetools_screen_resize.json", &file_exists);
}
if (fileutils::file_exists(this->config_path)) {
if (file_exists) {
this->config_load();
}
}
@@ -29,12 +35,6 @@ namespace cfg {
}
void ScreenResize::config_load() {
if (SCREEN_RESIZE_CFG_PATH_OVERRIDE.has_value()) {
log_info("ScreenResize", "loading custom config: {}", this->config_path.string());
} else {
log_info("ScreenResize", "loading global config from APPDATA");
}
std::string config = fileutils::text_read(this->config_path);
if (config.empty()) {
log_info("ScreenResize", "config is empty");
@@ -87,6 +87,10 @@ namespace cfg {
load_float_value(doc, root + prefix + "scale_x", scene.scale_x);
load_float_value(doc, root + prefix + "scale_y", scene.scale_y);
load_bool_value(doc, root + prefix + "keep_aspect_ratio", scene.keep_aspect_ratio);
int duplicate = 0;
load_int_value(doc, root + prefix + "duplicate", duplicate);
scene.duplicate = static_cast<cfg::ScreenDuplicateMode>(duplicate);
}
// windowed settings are always under game settings
@@ -170,8 +174,6 @@ namespace cfg {
}
void ScreenResize::config_save() {
log_info("ScreenResize", "saving config: {}", this->config_path.string());
rapidjson::Document doc;
std::string config = fileutils::text_read(this->config_path);
if (!config.empty()) {
@@ -203,6 +205,7 @@ namespace cfg {
rapidjson::Pointer(root + prefix + "scale_x").Set(doc, scene.scale_x);
rapidjson::Pointer(root + prefix + "scale_y").Set(doc, scene.scale_y);
rapidjson::Pointer(root + prefix + "keep_aspect_ratio").Set(doc, scene.keep_aspect_ratio);
rapidjson::Pointer(root + prefix + "duplicate").Set(doc, scene.duplicate);
}
// windowed mode settings
@@ -221,10 +224,10 @@ namespace cfg {
doc.Accept(writer);
// save to file
if (fileutils::text_write(this->config_path, buffer.GetString())) {
if (fileutils::write_config_file("ScreenResize", this->config_path, buffer.GetString())) {
// this->config_dirty = false;
} else {
log_warning("ScreenResize", "unable to save config file to {}", this->config_path.string());
log_warning("ScreenResize", "unable to save config file");
}
}
}
+7
View File
@@ -14,12 +14,19 @@ namespace cfg {
ResizableFrame = 2
};
enum ScreenDuplicateMode {
None = 0,
CopyLeft = 1,
CopyRight = 2,
};
struct fullscreen_setting {
int offset_x = 0;
int offset_y = 0;
float scale_x = 1.0;
float scale_y = 1.0;
bool keep_aspect_ratio = true;
ScreenDuplicateMode duplicate = ScreenDuplicateMode::None;
};
extern std::optional<std::string> SCREEN_RESIZE_CFG_PATH_OVERRIDE;
-701
View File
@@ -1,701 +0,0 @@
04/08/2025 [spice2x]
Ensure rawinput touch is default for all games (-wintouch to override)
Various overlay UI fixes and improvements, update ImGui library
Add missing lights in Museca
03/29/2025 [spice2x]
Add SDVX landscape mode, -forceresswap option
Fix bugs in image scaler
Configurator UI tweaks
03/25/2025 [spice2x]
Add Mahjong Fight Girl support
Add option for custom full screen resolution (-forceres)
03/24/2025 [spice2x]
Auto PIN entry
Multiple scenes for image resize
API: add resize function, lights.read can now filter lights by name
Fix DLL hooks not receiving command-line arguments
03/16/2025 [spice2x]
Fix clipboard copy function truncating last character of string
03/03/2025 [spice2x]
IIDX: add workaround for REVERB EX filter missing in Windows 11 update
02/25/2025 [spice2x]
Add -vsyncbuffer option for forcing double/triple buffering
02/22/2025 [spice2x]
Add -windowscale option for resizing DX9 backbuffer dimensions
01/26/2025 [spice2x]
Expanded main menu in overlay (default bind: esc key)
Nostalgia: touch and swipe gestures (-nostpoke)
Jubeat: detect root directory boot failure
Add "Any Direction" analog type for button binding
01/16/2025 [spice2x]
More MIDI improvements:
use v2_drum algorithm instead of v2 by default for drummania & FTT
threshold setting for CC when mapped as a button (hi-hat pedals)
sustain duration applied to CC OnOff controls (sustain pedals)
Nostalgia, Beatstream: fix EA card insertion issue
CCJ, QKS: translate some windowed mode settings to Unity engine
Fix DEBUG build
01/08/2025 [spice2x]
Better MIDI support; significant improvements for drums
Fix various UI bugs in Buttons / Analog tabs
Nostalgia: fix velocity handling for MIDI input
Show camera names in camera control overlay
Game exit dialog (default: Escape key)
Button layout help text in Buttons tab
12/31/2024 [spice2x]
Fix videos not playing in Unity-based games
Dump audio device info in wrapped WASAPI handler
Update DLL load failure message
12/30/2024 [spice2x]
Busou Shinki: Show Cursor option now properly shows cursor in game
IIDX: add IIDX NVENC Quality option (-iidxreccqp)
IIDX: always enable NVENC hook, not just when -iidx is set
IIDX: add help text to keypad overlay in TDJ mode
Busou Shinki, QKS, CCJ: Only show cursor by default if no touchscreen detected
Ignore invalid HID devices with bad device path
12/13/2024 [spice2x]
Improve error messages for DLL load failures
Load cardio module in spicecfg
12/06/2024 [spice2x]
Clean up seldom used features (VR, Lua scripting, layeredfs, KFControl)
New card scanner section in Cards tab
Add -scardfix option for converting NFC cards into E00401 format
Add system information logging, controlled by -sysdump option
11/20/2024 [spice2x]
DRS: add subscreen overlay window (dance floor tape LED display)
IIDX: TDJ cam selection options for users with 3+ cameras
(-iidxtdjcamhooktop /-iidxtdjcamhookfront)
IIDX/SDVX: move some options to Advanced tab
Update -nvprofile to set V-Sync settings as Application Controlled
10/29/2024 [spice2x]
(IIDX31+) apply signature patch to force WASAPI by default; use -iidxsounddevice to override
Fix SDVX EG song search when using Japanese touch keyboard (when OS is not ja-JP)
Nostalgia I/O improvements
Expose DDR Gold cabinet tape LEDs via Spice API
10/14/2024 [spice2x]
Apply patches earlier, on DLL load notification
SMX dedicab lights improvements
10/12/2024 [spice2x]
SMX dedicab lights support (requires forked SMX.dll - see Issue #228)
Option to show FPS overlay on top left (-fpsflip)
Allow TDJ camhook to load without -iidx (for cabs)
09/21/2024 [spice2x]
Add Inject Early DLL Hooks option (-z)
09/14/2024 [spice2x]
Add Patch Manager Config Path (-patchcfgpath) option
Vertical/horizontal flip for TDJ cam hook
Fix mouse button behavior when left/right buttons are swapped
08/24/2024 [spice2x]
Fix Patch Manager failing to import patches when modules path is Unicode
08/22/2024 [spice2x]
UI update for Card Manager in overlay
08/12/2024 [spice2x]
IIDX TDJ camera - bug fixes
Prevent users from accidentally enabling -cfg and -kfcontrol in spicecfg
08/07/2024 [spice2x]
IIDX TDJ camera improvements - draw modes (stretch/crop), bug fixes
08/06/2024 [spice2x]
IIDX TDJ camera improvements
Automatic cropping for 16:9 resolutions
Toggle to keep or override camera parameters
Fix Unicode path handling for config files
08/03/2024 [spice2x]
IIDX TDJ camera improvements
Camera control overlay (check Overlay tab)
Hardware acceleration for camera rendering
IIDX TDJ play record improvements
NVENC hooks have been added to fix crash on song start; -nod3d9devhook
is no longer required
07/29/2024 [spice2x]
Fix IIDX TDJ not launching in fullscreen with two monitors
07/28/2024 [spice2x]
Add more DLL support for -iidxtdjcamhook
Add -iidxtdjcamhookratio and -iidxtdjcamhookoffset
07/25/2024 [spice2x]
Add -iidxtdjcamhook option for webcam support in TDJ
07/17/2024 [spice2x]
Disable touch feedback indicators
Bug fixes
07/15/2024 [spice2x]
Fix touch and touch emulation in wintouch-based games (Nostalgia, BeatStream...)
Fix misaligned SpiceCompanion touches on windowed TDJ subscreen
Improvements to TDJ poke feature
07/13/2024 [spice2x]
Fix windowed TDJ subscreen not accepting mouse clicks after card in
Allow windowed TDJ/UFC subscreen to minimize, but not close
07/10/2024 [spice2x]
Better gfdm XG2 and XG3 support
IIDX TDJ windowed mode rendering improvements
IIDX TDJ subscreen poke feature
CardIO NumLock toggle option
Patch manager UI tweaks
07/06/2024 [spice2x]
Reliability improvements for config file saving
Experimental and incomplete support for gfdm XG3
Option to specify path for screen resize config
Reflec Beat: fix being unable to type into overlay
07/03/2024 [spice2x]
Fix SMX stage compatibility
Patch manager: log header in patches JSON, better error handling
06/26/2024 [spice2x]
Fix MDX-003 not retrieving patches from online source
06/22/2024 [spice2x]
Reliability & performance improvements for auto-card-insert
Update -sdvxnosub to prevent creation of subscreen in windowed mode
Bug fixes
06/20/2024 [spice2x]
Improve experience for windowed SDVX UFC (always launch subscreen window)
Add Delay option in analog binds
Fix pop'n soft-lock with auto card insert
Fix -graphics-single-adapter not working in Beatstream/Nostalgia
06/14/2024 [spice2x]
Improve experience for touch-enabled games in windowed mode (SpiceTouch)
06/08/2024 [spice2x]
Patch manager bug fixes
06/03/2024 [spice2x]
Fix patch status not being saved properly
Search tab for finding options
06/02/2024 [spice2x]
Fix BBC and Museca hang on boot when COM ports are present
Deprecate -iidxtdjw (use -iidxtdj and -w together instead)
05/31/2024 [spice2x]
Redesign patches tab
Bug fixes for patches.json parsing
05/29/2024 [spice2x]
Add support for "number" patch type
History for remote patch URLs
05/28/2024 [spice2x]
Update -iidxtdjw to hide useless second window
05/27/2024 [spice2x]
Fix -apiserial crashing when -apiserialbaud is not provided
Suppress "failed to acquire subscreen" error when not relevant
05/22/2024 [spice2x]
Add caution string to patches.json format
Paste from clipboard button for URL patches import
05/12/2024 [spice2x]
Disable URL patch importing in-game
UI tweaks for importing patches
05/05/2024 [spice2x]
UI tweaks for importing patches
Add aliases for arguments with -sp2x prefix
05/04/2024 [spice2x]
New patch JSON format, importing patches from URL
Add Process Efficiency Class option for hetero CPUs
04/29/2024 [spice2x]
Move -vr to be DANCERUSH only option
Fix being unable to type into overlay when -nolegacy is on
04/08/2024 [spice2x]
Fix Discord rich presence app IDs
03/31/2024 [spice2x]
Fix IC CARD UNIT ERROR in UFC when COM ports are present
03/24/2024 [spice2x]
Fix Ongaku Paradise crash on boot due to VFS redirection
Address all compiler warnings
03/17/2024 [spice2x]
SMX pad lights output
Fix DDR pad input for P4IO / BIO2
Option aliases for IIDX/SDVX subscreen disable
03/16/2024 [spice2x]
I/O for DDR white cabinet type (P4IO)
Remove background animation from configurator
Compiler updates
03/06/2024 [spice2x]
Lights output for DDR gold cabinet type
02/13/2024 [spice2x]
Fix lights not updating over API in configurator
CCJ mouse trackball improvements
Make IIDX/SDVX native touch the default when -sp2x-nod3d9devhook is on
Remove log spam in IIDX
02/02/2024 [spice2x]
Add missing NVCUDA stubs for cuStreamCreate and cuStreamDestroy_v2
02/01/2024 [spice2x]
QuizKnock STADIUM support
Busou Shinki analog joystick support
01/30/2024 [spice2x]
Add woofer lights to popn HD mode
Prevent crash when both -graphics-force-single-adapter and
-sp2x-nod3d9devhook are enabled
01/27/2024 [spice2x]
Add Disable D3D9 Device Hook option (-sp2x-nod3d9devhook)
01/20/2024 [spice2x]
Force Exit overlay hotkey
Improve Low Latency Audio to work with more games
Charge Machine I/O fixes
UI tweaks (API tab, Development tab)
01/07/2024 [spice2x]
Chase Chase Jokers improvements (trackball and vsync fix)
01/06/2024 [spice2x]
Chase Chase Jokers improvements (cmd line args, trackball sensitivity)
01/05/2024 [spice2x]
Chase Chase Jokers support
01/01/2024 [spice2x]
UI tweaks - menu bar in configurator window
IIDX: scan for SOUND_OUTPUT_DEVICE in binary and log message
Switch from -Ofast to -O2
Remove ImGui demo and debug files from release (reduced binary size)
12/30/2023 [spice2x]
Add NVAPI Block option (-sp2x-nonvapi)
SDVX: Auto Card Insert disables itself after a timeout to prevent soft lock
12/27/2023 [spice2x]
Prevent SDVX from dumping PATH variable to log
Stubs for NVIDIA DLLs (nvcuda.dll, nvcuvid.dll, nvEncodeAPI64.dll)
UI tweaks
12/25/2023 [spice2x]
Fix Lock Cursor option not updating capture area in windowed mode
Update TDJ rom file hooks
12/23/2023 [spice2x]
Remove the need for hex edits to fix note scroll speed in SDVX VM on NVIDIA
Improvements to -graphics-force-refresh
12/22/2023 [spice2x]
HID analog relative axis mode
Automatic process affinity fix for Gitadora
Fix -cfgpath option not working
Buttons tab UI fix for Bind Many
12/18/2023 [spice2x]
Fix Road Fighters 3D I/O error on boot
12/17/2023 [spice2x]
Integer multiplier / divisor for HID analog
(alternative to existing sensitivity option)
12/15/2023 [spice2x]
Add hooks to prevent display scaling changes when SDVX launches
12/12/2023 [spice2x]
Add support for keypad in SDVX UFC/VM mode
New DX9on12 flag with more options (-sp2x-dx9on12, replaces -9on12)
12/11/2023 [spice2x]
Fix crash at launch on AMD/Intel GPUs when nvapi DLL is present
12/09/2023 [spice2x]
Lights output for Gitadora
Lights output for DRS
Tape LED averaging algorithm option (-sp2x-tapeledalgo)
Fix occasional crash on shutdown
11/23/2023 [spice2x]
Low latency shared audio option (-sp2x-lowlatencysharedaudio)
Automatic dev/raw/* folder creation for popn19-21
Move overlay bindings to Overlay tab
11/17/2023 [spice2x]
Use E00401 prefix for card number generation
Volume API hooks to prevent audio volume change (enabled by default)
Small UI tweaks
11/15/2023 [spice2x]
Automatic card insert option (-sp2x-autocard)
Dump CPU features to log on startup
11/12/2023 [spice2x]
Fix crash in some games caused by linker changes in 2022
(Gitadora Exchain and older, Bone Eater, etc)
Add IIDX native touch option (-sp2x-iidxnativetouch)
Fix V-Sync issues with auto-screen-orientation
11/04/2023 [spice2x]
Option for NVIDIA GPU optimization (-sp2x-nvprofile)
SmartCard fixes (for -scardflip and -scardtoggle)
Small reorganization of options in configurator
10/30/2023 [spice2x]
Add touch input support for DRS
Add TDJ Windowed mode for IIDX (-sp2x-iidxtdjw)
Fix -iidxasio not working in some versions of IIDX
10/22/2023 [spice2x]
Add E-spec I/O emulation for IIDX 30+
Fix IIDX TDJ launching at 60Hz for some users
Add workaround for buggy ASIO drivers that lock up on close
Hide Insert Card overlay by default, add option to show it again
09/29/2023 [spice2x]
Fix subscreen not updating in certain versions of EG.
Detect long paths and log a warning message.
06/08/2023 [spice2x]
Improved touch support for Jubeat and Reflec Beat.
04/23/2023 [spice2x]
Update -iidxsounddevice to automatically pick a value (wasapi in most cases)
04/21/2023 [spice2x]
Fix spicecfg not launching in some specific cases
04/20/2023 [spice2x]
Update font for IIDX segment display
New option: auto show FPS window
04/16/2023 [spice2x]
New feature: window resize. Added new options to change window size and
position on launch. Screen Resize window (F11) also updated with new
controls
Screen Resize settings are now per-game
Misc bug fixes
04/09/2023 [spice2x]
Add IIDX LED ticker (segment display) as subscreen overlay for LDJ
Add I/O panel window for all games, special support for IIDX, DDR, GFDM
Add options to automatically show certain windows on game launch
04/03/2023 [spice2x]
Move/resize for IIDX/SDVX subscreen overlay window
New options to control subscreen overlay window
Turn common config mistakes into fatal error messages
Various bug fixes for touch handling
Revert "pop'n launching at desktop refresh rate" hack from 03/30/2023
03/30/2023 [spice2x]
New option: IIDX Force Definition Type HD (720p)
New option: process priority
Fix pop'n launching at desktop refresh rate
Lower logger thread priority to reduce stutters
Misc bug fixes
03/25/2023 [spice2x]
Screen Resize UI update (F11)
FPS UI update (F12)
New option: processor affinity
Misc bug fixes
03/20/2023 [spice2x]
ASIO device override for SDVX (experimental)
Fix subscreen overlay for SDVX in Valkyrie mode using BI2X I/O
Update text for touch and audio config options
UI tweaks and minor bug fixes
03/17/2023 [spice2x]
Disable UPX (file sizes will be larger)
Fix regression from 2022 that prevented early patches
from being loaded properly (such as audio hex edits)
New option: HID lighting brightness
New option: display auto-rotate direction
New option: custom sleep duration
03/15/2023 [spice2x]
Improve IIDX TT+/- handling (allow alternate key bind)
Fix SpiceCompanion touch targets for TDJ FHD
Config UI tweaks
03/13/2023 [spice2x]
Fix TDJ FHD launching at wrong refresh rate
New IIDX/SDVX options for TT/knob sensitivity when using digital input
03/12/2023 [spice2x]
Fix mouse input for SDVX EG subscreen overlay
New unified layout for Options tab
Allow multiple DLLs in Inject Hook field
03/11/2023 [spice2x]
Beginning of spice2x fork, based on another fork
Ensure TDJ mode runs at 1080p@120Hz for newer games
Fix IIDX subscreen overlay in FHD mode
Gracefully deal with bad ea3-config.xml
04/02/2022 - Add lua scripting
Add button edit dialog
Add button debounce/invert settings
Add bind many function
Misc bug fixes
12/28/2021 - Add DirectX 9On12 wrapper option
Add analog deadzone settings
Fix SSL handshaking failed issue
Misc bug fixes
12/19/2021 - Force single graphics adapter option
Performance improvements
Misc bug fixes
10/12/2021 - Ongaku Paradise support
Fixed memory leak in Windows 8+ touch handler
Fix Gitadora two channel fix for Nextage
Fix Charge Machine touch window position
IIDX 19 support
04/02/2021 - Charge Machine support
Fix "unsupported fs type" endless loop
Misc bug fixes
03/01/2021 - Add DDR HD speaker lights
Automatic /dev/nvram and /dev/raw creation for AVS 2.17 and later
Fix FlexASIO crash introduced in previous release
Fix overlay crash from ImGui navigation button handling
Fix AVS configuration reading with binary files
Add AVS VFS drive remapping for E: and F:
Improve XP compatibility
Force dummy audio when using ASIO backend
Misc bug fixes
12/30/2020 - Metal Gear Arcade lights and fixes
Misc bug fixes
12/11/2020 - Fix patch manager crashing on invalid address
Automatic /dev/nvram and /dev/raw creation for AVS 2.16 and prior
Analog sensitivity improvements
Locale improvements
Misc bug fixes
10/27/2020 - Screen Capture support
IIDX TDJ support
Updated patches
Misc bug fixes
10/13/2020 - Configurator patches tab
KFControl fix cursor wrap
Screen resize window
Misc bug fixes
10/03/2020 - Add KFControl
Misc bug fixes
09/27/2020 - Reflec limelight & colette (LBR) support
LovePlus lights support
Fix module loading when spice(64).exe is outside contents
Ignore touchpad HID devices in touchscreen detection
Keypad bindings
SSL support
Analog knob preview
06/15/2020 - QMA physical keyboard support
Misc bug fixes
05/19/2020 - Alternate audio backends for exclusive-mode applications
Fix screenshot handler crash in KMA and LMA
MessageBox hooks (stops fullscreen windows from losing focus)
04/25/2020 - pop'n music 19 & 20 support
DDR X2 support
pop'n & HPM HDMI fix
Misc additions, fixes
03/13/2020 - Misc bug fixes and improvements
03/10/2020 - Winning Eleven support
03/04/2020 - Tenkaichi Shogikai support
Otoca D'or support
Misc bug fixes
02/29/2020 - MIDI hotplugging / overlay
Misc bug fixes
02/04/2020 - Configurator options tab
Overlay button bindings
Card generator
Misc bug fixes
01/22/2020 - Audio improvements
SDVX print overwrite option
PNM HD/SD options
Window icons
Misc improvements
01/11/2020 - New Config Utility (F4)
Audio Hooks
Misc bug fixes
01/03/2020 - DANCERUSH STARDOM support
Chinese SDVX support
Nostalgia OP2 support
LayeredFS now complete
Misc bug fixes
11/26/2019 - LayeredFS integration
Misc bug fixes
11/17/2019 - Screenshots (PrtScr Key)
DLL Architecture detection
SCard Felica support
Improve MIDI performance
Fix bt5api error handling
Fix XP support
11/05/2019 - Gitadora XG3 support
Guitar motor support
Gitadora cabinet setting
DDR HD->SD light support
ESP8266/NodeMCU API support
Misc bug fixes
10/13/2019 - Serial API support
Fullscreen monitor setting
Misc bug fixes
09/30/2019 - Fixed verbose D3D9 logging
MiniMaid support
EA-Dev functions
Metal Gear bug fixes
09/26/2019 - Steel Chronicle support
ACIO buffer view/editor
Misc bug fixes
09/21/2019 - IIDX Effector View
Performance improvements
PCBID now hidden in log file
Misc bug fixes
09/16/2019 - Introducing SpiceOverlay
- Virtual Keypads/Cards
- Patch Manager
- FPS/Log/Control Windows
Pop'n Music fancy window mode
Optional app-config.xml
Misc improvements
Misc bug fixes
09/09/2019 - DDR BIO2 support
NVIDIA Optimus support
Reflec Beat faster loading
Performance improvements
Misc bug fixes
08/24/2019 - Automatic G-Sync disable
Fixed Scotto inverted keypad
Fixed SDVX Gen1 input drops
Performance improvements
08/19/2019 - Scotto support
Mahjong Fight Club support
FutureTomTom Kinect fix
Fixed AVS heap size defaults
Fixed signal hook crash
Fixed early boot logging
08/14/2019 - HELLO! Pop'n Music lights
Improve performance
08/13/2019 - Add -cfgpath option
Misc additions/fixes
08/10/2019 - HELLO! Pop'n Music support
LovePlus support
Discord Rich Presence support
API shutdown/reboot
Add -nolegacy option
Colored logging
07/31/2019 - QMA windowed mode fix
QMA network check fix
Logging improvements
07/22/2019 - DDR SD mode reader fix
Misc bugs fixed with ACIO
Beatstream lights/mouse
Beatstream -ea support
07/19/2019 - FutureTomTom support
Reflec Beat lights
Dance Evolution lights
Nostalgia lights
SuperExit on ALT-F4
07/16/2019 - Hook DLLs load earlier
Heap size argument alias
API restart function
SmartEA cmd line option
Reader keypad support
Initial MFC support
Misc bug fixes
06/22/2019 - Touchpad button binding fix
MIDI buttons/analogs support
Misc bug fixes
05/18/2019 - API WebSocket support
05/16/2019 - Device Hotplugging
USB-Stick card inserts
Cursor capture fix
EA server fix for old AVS
05/07/2019 - Reflec 1 support
Gitadora custom PCBID
Touchscreen improvements
Touchscreen support on XP
Higher RB input polling rate
L/R-Shift/Control support
04/13/2019 - DDR SD Mode lights
Sextet (LIT Board) support
PIUIO hardware support
Windowed mode improvements
03/14/2019 - IIDX API module
EA server maintenance toggle
SDVX BI2A support
02/19/2019 - Network API server
Run outside of modules dir
Misc bug fixes
01/27/2019 - Jubeat Knit support
Quiz Magic Academy support
Museca/Jubeat lights
Smartcard NumLock toggle
01/11/2019 - Open Source!
Jubeat Copious support
Bishi Bashi Channel support
PacDrive/PocketVoltex support
KBT/KLD now are optional
Stacktraces, quiet crash
Huge code refactoring
Stability improvements/fixes
11/03/2018 - IIDX 25 (io, cams, light)
IIDX effector bindings
HID lighting output support
Button Multibinding
HID readers e.g. zyp's cardio
Bug fixes as usual
08/17/2018 - DDR X3, Nostalgia, Network
06/25/2018 - HID smartcard readers
06/10/2018 - DDR support
06/07/2018 - Gitadora/HID/EA bug fixes
05/18/2018 - Gitadora and MIDI devices
05/07/2018 - Support for hat switches
04/21/2018 - Card path configuration
04/09/2018 - HID/Analog/RawInput support
03/13/2018 - SDVX printer, felica, BT5API
01/23/2018 - MUSECA analog slowdown, fixes
01/21/2018 - Many fixed bugs/crashes
11/15/2017 - Fixed the MUSECA sound issue
11/14/2017 - Added MUSECA, Pop'n Music
Wavepass reader support
09/11/2017 - Added Road Fighters 3D
08/29/2017 - Custom modules path,
UAC admin manifest, netfix
08/28/2017 - Bug fixes
08/21/2017 - 64bit builds, beatstream
More command line options
07/28/2017 - Fixed I/O for IIDX and SDVX
07/26/2017 - Configuration Tool, Jubeat
Touch module, more options
06/01/2017 - Added DEA support
05/31/2017 - Public release
+2 -1
View File
@@ -11,6 +11,7 @@ RUN pacman --noconfirm -Syu git \
mingw-w64-winpthreads \
mingw-w64-gcc \
mingw-w64-headers \
mingw-w64-binutils
mingw-w64-binutils \
ccache
RUN useradd user -m && echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
RUN su user -c "cd /tmp/ && git clone https://aur.archlinux.org/yay-bin.git --depth=1 && cd yay-bin && makepkg --noconfirm -si && yay --noconfirm -S mingw-w64-cmake"
+4
View File
@@ -1527,8 +1527,10 @@ FMT_CONSTEXPR auto make_arg(const T& value) -> basic_format_arg<Context> {
return arg;
}
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-reference"
#endif
// The type template parameter is there to avoid an ODR violation when using
// a fallback formatter in one translation unit and an implicit conversion in
@@ -1544,7 +1546,9 @@ FMT_CONSTEXPR FMT_INLINE auto make_arg(const T& val) -> value<Context> {
return arg;
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
template <bool IS_PACKED, typename Context, type, typename T,
FMT_ENABLE_IF(!IS_PACKED)>
+1 -1
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.9)
cmake_minimum_required(VERSION 3.12)
project(imgui CXX)
set(HASH_LIBRARY_HEADERS
+1 -1
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.9)
cmake_minimum_required(VERSION 3.12)
cmake_policy(SET CMP0069 NEW)
project(minhook)
+3
View File
@@ -23,6 +23,7 @@
* Modified version.
*/
#ifndef NO_SCARD
#include "scard.h"
#include <windows.h>
@@ -409,3 +410,5 @@ void scard_threadstart() {
void scard_fini() {
should_exit = true;
}
#endif
+1 -1
View File
@@ -14,7 +14,7 @@ namespace games::ccj {
bool MOUSE_TRACKBALL_USE_TOGGLE = false;
uint8_t TRACKBALL_SENSITIVITY = 10;
static HANDLE fakeHandle = (HANDLE)0xDEADBEEF;
static HANDLE fakeHandle = (HANDLE)0xDEADBEEFull;
static HWND hWnd = nullptr;
static WNDPROC wndProc = nullptr;
static std::thread *tbThread = nullptr;
+115 -10
View File
@@ -2,6 +2,8 @@
#include "acioemu/handle.h"
#include "avs/game.h"
#include "hooks/avshook.h"
#include "hooks/cfgmgr32hook.h"
#include "hooks/devicehook.h"
#include "hooks/setupapihook.h"
#include "hooks/sleephook.h"
@@ -27,6 +29,7 @@ namespace games::ddr {
// settings
bool SDMODE = false;
bool NO_CODEC_REGISTRATION = false;
uint8_t DDR_TAPELEDS[TAPELED_DEVICE_COUNT][50][3] {};
@@ -49,12 +52,82 @@ namespace games::ddr {
return SendMessage_real(hWnd, Msg, wParam, lParam);
}
bool contains_only_ascii(const std::string& str) {
for (auto c: str) {
if (static_cast<unsigned char>(c) > 127) {
return false;
}
}
return true;
}
DDRGame::DDRGame() : Game("Dance Dance Revolution") {
}
void DDRGame::register_codecs() {
// find where spice.exe / spice64.exe is located
const auto &spice_bin_path = libutils::module_file_name(nullptr).parent_path();
// find the com directory
std::filesystem::path dir = "";
if (MODULE_PATH == spice_bin_path) {
// try: \com
dir = spice_bin_path / "com";
} else {
// try: modules\..\com
dir = MODULE_PATH / ".." / "com";
}
if (fileutils::dir_exists(dir)) {
log_info("ddr", "looking for codecs in this directory: {}", dir.string());
} else {
log_info("ddr", "codecs directory not found: {}", dir.string());
return;
}
for (const auto &file : std::filesystem::directory_iterator(dir)) {
const auto &filename = file.path().filename();
const auto extension = strtolower(filename.extension().string());
if (extension != ".dll") {
continue;
}
log_info("ddr", "found DLL: {}, size: {} bytes", filename.string(), file.file_size());
if (filename == "k-clvsd.dll" || filename.string().find("xactengine") == 0) {
const std::wstring wcmd = L"regsvr32.exe /s \"" + file.path().wstring() + L"\"";
const std::string cmd = "regsvr32.exe /s \"" + file.path().string() + "\"";
int result = 0;
std::thread t([wcmd, &result]() {
result = _wsystem(wcmd.c_str());
});
t.join();
if (result == 0) {
log_info("ddr", "`{}` returned {}", cmd, result);
} else {
log_warning("ddr", "`{}` failed, returned {}", cmd, result);
}
if (!contains_only_ascii(file.path().string())) {
log_warning(
"ddr",
"BAD PATH ERROR\n\n\n"
"!!! !!!\n"
"!!! filesystem path to codec contains non-ASCII characters! !!!\n"
"!!! this may cause the game to crash! !!!\n"
"!!! !!!\n"
);
}
}
}
}
void DDRGame::pre_attach() {
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("TDX")) {
log_fatal(
log_warning(
"ddr",
"BAD MODEL NAME ERROR\n\n\n"
"!!! model name set to TDX, this is WRONG and will break your game !!!\n"
@@ -65,6 +138,8 @@ namespace games::ddr {
"!!! !!!\n"
"!!! model name set to TDX, this is WRONG and will break your game !!!\n\n\n"
);
log_fatal("ddr", "BAD MODEL NAME ERROR - TDX specified, should be MDX instead");
}
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::DEST[0] == 'U') {
@@ -87,6 +162,17 @@ namespace games::ddr {
"!!! !!!\n\n\n"
);
}
if (!cfg::CONFIGURATOR_STANDALONE) {
if (!NO_CODEC_REGISTRATION) {
this->register_codecs();
} else {
log_warning(
"ddr",
"skipping codec registration (-ddrnocodec), "
"game may crash if you didn't register codecs before launching the game");
}
}
}
void DDRGame::attach() {
@@ -99,10 +185,37 @@ namespace games::ddr {
// init device hook
devicehook_init();
// init SETUP API
setupapihook_init(avs::game::DLL_INSTANCE);
// DDR ACE actually uses another DLL for things
if (game_mdx != nullptr) {
setupapihook_init(game_mdx);
}
// add fake devices
if (avs::game::DLL_NAME == "arkmdxbio2.dll") {
devicehook_add(new acioemu::ACIOHandle(L"COM1"));
} else if(avs::game::DLL_NAME == "arkmdxp4.dll") {
if (avs::game::SPEC[0] == 'I') {
// settings (bio2)
SETUPAPI_SETTINGS settingsbio2 {};
const char property2[] = "BIO2(VIDEO)";
settingsbio2.class_guid[0] = 0x4D36E978;
settingsbio2.class_guid[1] = 0x11CEE325;
settingsbio2.class_guid[2] = 0x8C1BF;
settingsbio2.class_guid[3] = 0x1803E12B;
memcpy(settingsbio2.property_devicedesc, property2, sizeof(property2));
setupapihook_add(settingsbio2);
// cfgmgr (bio2)
CFGMGR32_HOOK_SETTING settingbio2 {};
settingbio2.device_id = "USB\\VID_1CCF&PID_804C";
settingbio2.device_node_id = "USB\\VID_1CCF&PID_804C&MI_00\\?&????????&?&????";
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
cfgmgr32hook_add(settingbio2);
}
} else if (avs::game::DLL_NAME == "arkmdxp4.dll") {
devicehook_add(new DDRP4IOHandle());
} else {
devicehook_add(new DDRFOOTHandle());
@@ -144,14 +257,6 @@ namespace games::ddr {
memcpy(settingsp4io.property_devicedesc, settings_property, strlen(settings_property) + 1);
memcpy(settingsp4io.interface_detail, settings_detail_p4io, sizeof(settings_detail_p4io));
// init SETUP API
setupapihook_init(avs::game::DLL_INSTANCE);
// DDR ACE actually uses another DLL for things
if (game_mdx != nullptr) {
setupapihook_init(game_mdx);
}
// add settings
setupapihook_add(settings1);
setupapihook_add(settings2);
+4
View File
@@ -7,6 +7,7 @@ namespace games::ddr {
// settings
extern bool SDMODE;
extern bool NO_CODEC_REGISTRATION;
// Buffers to store RGB data for tape LEDs on gold cabinets
const size_t TAPELED_DEVICE_COUNT = 11;
@@ -18,5 +19,8 @@ namespace games::ddr {
virtual void pre_attach() override;
virtual void attach() override;
virtual void detach() override;
private:
void register_codecs();
};
}
+10 -33
View File
@@ -409,50 +409,27 @@ namespace games::iidx {
* 16 - side panel right - 183 bytes - 61 colors
*
* data is stored in RGB order, 3 bytes per color
*
* TODO: expose this data via API
*/
// data mapping
static struct TapeLedMapping {
size_t data_size;
int index_r, index_g, index_b;
TapeLedMapping(size_t data_size, int index_r, int index_g, int index_b)
: data_size(data_size), index_r(index_r), index_g(index_g), index_b(index_b) {}
} mapping[] = {
{ 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB },
{ 19, Lights::StageRightAvgR, Lights::StageRightAvgG, Lights::StageRightAvgB },
{ 45, Lights::CabinetLeftAvgR, Lights::CabinetLeftAvgG, Lights::CabinetLeftAvgB },
{ 45, Lights::CabinetRightAvgR, Lights::CabinetRightAvgG, Lights::CabinetRightAvgB },
{ 21, Lights::ControlPanelUnderAvgR, Lights::ControlPanelUnderAvgG, Lights::ControlPanelUnderAvgB },
{ 54, Lights::CeilingLeftAvgR, Lights::CeilingLeftAvgG, Lights::CeilingLeftAvgB },
{ 11, Lights::TitleLeftAvgR, Lights::TitleLeftAvgG, Lights::TitleLeftAvgB },
{ 11, Lights::TitleRightAvgR, Lights::TitleRightAvgG, Lights::TitleRightAvgB },
{ 54, Lights::CeilingRightAvgR, Lights::CeilingRightAvgG, Lights::CeilingRightAvgB },
{ 17, Lights::TouchPanelLeftAvgR, Lights::TouchPanelLeftAvgG, Lights::TouchPanelLeftAvgB },
{ 17, Lights::TouchPanelRightAvgR, Lights::TouchPanelRightAvgG, Lights::TouchPanelRightAvgB },
{ 68, Lights::SidePanelLeftInnerAvgR, Lights::SidePanelLeftInnerAvgG, Lights::SidePanelLeftInnerAvgB },
{ 68, Lights::SidePanelLeftOuterAvgR, Lights::SidePanelLeftOuterAvgG, Lights::SidePanelLeftOuterAvgB },
{ 61, Lights::SidePanelLeftAvgR, Lights::SidePanelLeftAvgG, Lights::SidePanelLeftAvgB },
{ 68, Lights::SidePanelRightOuterAvgR, Lights::SidePanelRightOuterAvgG, Lights::SidePanelRightOuterAvgB },
{ 68, Lights::SidePanelRightInnerAvgR, Lights::SidePanelRightInnerAvgG, Lights::SidePanelRightInnerAvgB },
{ 61, Lights::SidePanelRightAvgR, Lights::SidePanelRightAvgG, Lights::SidePanelRightAvgB },
};
// check index bounds
if (tapeledutils::is_enabled() && index < std::size(mapping)) {
auto &map = mapping[index];
if (tapeledutils::is_enabled() && index < std::size(TAPELED_MAPPING)) {
auto &map = TAPELED_MAPPING[index];
const auto data_size = map.data.capacity();
// pick a color to use
const auto rgb = tapeledutils::pick_color_from_led_tape(data, map.data_size);
const auto rgb = tapeledutils::pick_color_from_led_tape(data, data_size);
// program the lights into API
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], rgb.r);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b);
for (unsigned int i = 0; i < data_size; ++i) {
map.data[i].r = data[i * 3];
map.data[i].g = data[i * 3 + 1];
map.data[i].b = data[i * 3 + 2];
}
}
if (This != custom_node) {
+77 -74
View File
@@ -22,35 +22,31 @@
static std::filesystem::path dll_path;
static HMODULE iidx_module;
static uintptr_t addr_hook_a = 0;
static uintptr_t addr_hook_b = 0;
static uintptr_t addr_textures = 0;
static uintptr_t addr_camera_manager = 0;
static uintptr_t addr_device_offset = 0;
static uintptr_t addr_afp_texture_offset = 0;
typedef void **(__fastcall *camera_hook_a_t)(PBYTE);
typedef LPDIRECT3DTEXTURE9 (*camera_hook_b_t)(void*, int);
static camera_hook_a_t camera_hook_a_orig = nullptr;
static camera_hook_b_t camera_hook_b_orig = nullptr;
auto static hook_a_init = std::once_flag {};
static LPDIRECT3DDEVICE9EX device;
static LPDIRECT3DTEXTURE9 *texture_a = nullptr;
static LPDIRECT3DTEXTURE9 *texture_b = nullptr;
static LPDIRECT3DTEXTURE9 *camera_texture_a = nullptr;
static LPDIRECT3DTEXTURE9 *camera_texture_b = nullptr;
static LPDIRECT3DTEXTURE9 *preview_texture_a = nullptr;
static LPDIRECT3DTEXTURE9 *preview_texture_b = nullptr;
struct PredefinedHook {
std::string pe_identifier;
uintptr_t hook_a;
uintptr_t hook_b;
uintptr_t hook_textures;
uintptr_t hook_camera_manager;
uintptr_t hook_device_offset;
uintptr_t hook_afp_texture_offset;
};
PredefinedHook g_predefinedHooks[] =
{
// 27 003
{ "5f713b52_6d4090", 0x005971b0, 0x005fb2c0, 0x04e49898, 0x000000d0 },
// 27 010
{ "5f713946_7f52b0", 0x006b89e0, 0x0071c950, 0x04fd08b8, 0x000000d0 },
};
PredefinedHook g_predefinedHooks[] = {};
const DWORD g_predefinedHooksLength = ARRAYSIZE(g_predefinedHooks);
@@ -60,7 +56,7 @@ namespace games::iidx {
static IIDXLocalCamera *front_camera = nullptr; // camera id #1
std::vector<IIDXLocalCamera*> LOCAL_CAMERA_LIST = {};
static IDirect3DDeviceManager9 *s_pD3DManager = nullptr;
std::filesystem::path CAMERA_CONFIG_PATH = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools_camera_control.json";
std::filesystem::path CAMERA_CONFIG_PATH;
bool CAMERA_READY = false;
bool parse_cmd_params() {
@@ -75,8 +71,8 @@ namespace games::iidx {
if (sscanf(
s.c_str(),
"%i,%i,%i,%i",
(int*)&addr_hook_a, (int*)&addr_hook_b, (int*)&addr_textures, (int*)&addr_device_offset) == 4) {
"%i,%i,%i,%i,%i",
(int*)&addr_hook_a, (int*)&addr_textures, (int*)&addr_camera_manager, (int*)&addr_device_offset, (int*)&addr_afp_texture_offset) == 5) {
return true;
@@ -101,8 +97,8 @@ namespace games::iidx {
// there are three different ways we try to hook the camera entry points
// 1) user-provided override via cmd line
// 2) predefined offsets using PE header matching (needed for iidx27 and few others)
// 3) signature match (should work for most iidx28-31)
// 2) predefined offsets using PE header matching
// 3) signature match (should work for most iidx27-33)
// method 1: user provided
if (TDJ_CAMERA_OVERRIDE.has_value() && parse_cmd_params()) {
@@ -117,10 +113,11 @@ namespace games::iidx {
for (DWORD i = 0; i < g_predefinedHooksLength; i++) {
if (pe.compare(g_predefinedHooks[i].pe_identifier) == 0) {
log_misc("iidx:camhook", "Found predefined addresses");
addr_hook_a = g_predefinedHooks[i].hook_a;
addr_hook_b = g_predefinedHooks[i].hook_b;
addr_textures = g_predefinedHooks[i].hook_textures;
addr_device_offset = g_predefinedHooks[i].hook_device_offset;
addr_hook_a = g_predefinedHooks[i].hook_a;
addr_textures = g_predefinedHooks[i].hook_textures;
addr_camera_manager = g_predefinedHooks[i].hook_camera_manager;
addr_device_offset = g_predefinedHooks[i].hook_device_offset;
addr_afp_texture_offset = g_predefinedHooks[i].hook_afp_texture_offset;
return TRUE;
}
}
@@ -131,8 +128,8 @@ namespace games::iidx {
// --- addr_hook_a ---
uint8_t *addr_hook_a_ptr = reinterpret_cast<uint8_t *>(find_pattern(
iidx_module,
"488BC4565741564883EC5048C740D8FEFFFFFF",
"XXXXXXXXXXXXXXXXXXX",
"E800000000488B8F0000000048894D",
"X????XXX????XXX",
0, 0));
if (addr_hook_a_ptr == nullptr) {
@@ -141,24 +138,10 @@ namespace games::iidx {
return FALSE;
}
addr_hook_a = (uint64_t) (addr_hook_a_ptr - (uint8_t*) iidx_module);
// addr_hook_b
uint8_t* addr_hook_b_ptr = reinterpret_cast<uint8_t *>(find_pattern(
iidx_module,
"E8000000004885C07439E8",
"X????XXXXXX",
0, 0));
if (addr_hook_b_ptr == nullptr) {
log_warning("iidx:camhook", "failed to find hook: addr_hook_b");
return FALSE;
}
// displace with the content of wildcard bytes
int32_t disp_b = *((int32_t *) (addr_hook_b_ptr + 1));
int32_t disp_a = *((int32_t *) (addr_hook_a_ptr + 1));
addr_hook_b = (addr_hook_b_ptr - (uint8_t*) iidx_module) + disp_b + 5;
addr_hook_a = (addr_hook_a_ptr - (uint8_t*) iidx_module) + disp_a + 5;
// --- addr_textures ---
uint8_t* addr_textures_ptr = reinterpret_cast<uint8_t *>(find_pattern(
@@ -194,6 +177,28 @@ namespace games::iidx {
addr_textures = (addr_textures_ptr - (uint8_t*) iidx_module) + disp_textures + 7;
// --- addr_camera_manager ---
uint8_t *addr_camera_manager_ptr = reinterpret_cast<uint8_t *>(find_pattern(
iidx_module,
"E80000000033FF488B58",
"X????XXXXX",
0, 0));
if (addr_camera_manager_ptr == nullptr) {
log_warning("iidx:camhook", "failed to find hook: addr_get_camera_manager");
return FALSE;
}
// displace with the content of wildcard bytes
int32_t disp_camera_manager = *((int32_t *) (addr_camera_manager_ptr + 1));
addr_camera_manager_ptr = addr_camera_manager_ptr + disp_camera_manager + 5;
// once more to get the final address
disp_camera_manager = *((int32_t *) (addr_camera_manager_ptr + 3));
addr_camera_manager = (addr_camera_manager_ptr - (uint8_t*) iidx_module) + disp_camera_manager + 7;
// addr_device_offset
search_from = (addr_hook_a_ptr - (uint8_t*) iidx_module);
uint8_t *addr_device_ptr = reinterpret_cast<uint8_t *>(find_pattern_from(
@@ -201,39 +206,47 @@ namespace games::iidx {
"488B89",
"XXX",
3, 0, search_from));
addr_device_offset = *addr_device_ptr;
if (addr_textures_ptr == nullptr) {
log_warning("iidx:camhook", "failed to find hook: addr_textures (part 3)");
if (addr_device_ptr == nullptr) {
log_warning("iidx:camhook", "failed to find hook: addr_device_ptr");
return FALSE;
}
addr_device_offset = *addr_device_ptr;
// --- addr_afp_texture_offset ---
uint8_t *addr_afp_texture_ptr = reinterpret_cast<uint8_t *>(find_pattern(
iidx_module,
"488B41004885C07400488D5424",
"XXX?XXXX?XXXX",
0, 0));
if (addr_afp_texture_ptr == nullptr) {
log_warning("iidx:camhook", "failed to find hook: addr_afp_texture_offset");
return FALSE;
}
addr_afp_texture_offset = *(addr_afp_texture_ptr + 3);
return TRUE;
}
static void **__fastcall camera_hook_a(PBYTE a1) {
std::call_once(hook_a_init, [&]{
device = *reinterpret_cast<LPDIRECT3DDEVICE9EX*>(a1 + addr_device_offset);
auto const textures = *reinterpret_cast<LPDIRECT3DTEXTURE9**>((uint8_t*)iidx_module + addr_textures);
auto const preview = *reinterpret_cast<LPDIRECT3DTEXTURE9**>((uint8_t*)iidx_module + addr_textures);
auto const manager = reinterpret_cast<Camera::CCameraManager2*>((uint8_t*)iidx_module + addr_camera_manager);
texture_a = textures;
texture_b = textures + 2;
camera_texture_a = manager->cameras->a->d3d9_texture(addr_afp_texture_offset);
camera_texture_b = manager->cameras->b->d3d9_texture(addr_afp_texture_offset);
preview_texture_a = preview;
preview_texture_b = preview + 2;
init_local_camera();
});
return camera_hook_a_orig(a1);
}
static LPDIRECT3DTEXTURE9 camera_hook_b(void* a1, int idx) {
if (idx == 0 && top_camera) {
return top_camera->GetTexture();
}
if (idx == 1 && front_camera) {
return front_camera->GetTexture();
}
return camera_hook_b_orig(a1, idx);
}
bool create_hooks() {
auto *hook_a_ptr = reinterpret_cast<uint16_t *>(
reinterpret_cast<intptr_t>(iidx_module) + addr_hook_a);
@@ -246,17 +259,6 @@ namespace games::iidx {
return FALSE;
}
auto *hook_b_ptr = reinterpret_cast<uint16_t *>(
reinterpret_cast<intptr_t>(iidx_module) + addr_hook_b);
if (!detour::trampoline(
reinterpret_cast<camera_hook_b_t>(hook_b_ptr),
camera_hook_b,
&camera_hook_b_orig)) {
log_warning("iidx:camhook", "failed to trampoline hook_b");
return FALSE;
}
return TRUE;
}
@@ -443,7 +445,7 @@ namespace games::iidx {
}
IIDXLocalCamera* add_top_camera(IMFActivate* pActivate) {
auto top_camera = new IIDXLocalCamera("top", TDJ_CAMERA_PREFER_16_9, pActivate, s_pD3DManager, device, texture_a);
auto top_camera = new IIDXLocalCamera("top", TDJ_CAMERA_PREFER_16_9, pActivate, s_pD3DManager, device, camera_texture_a, preview_texture_a);
if (top_camera->m_initialized) {
LOCAL_CAMERA_LIST.push_back(top_camera);
} else {
@@ -454,7 +456,7 @@ namespace games::iidx {
}
IIDXLocalCamera* add_front_camera(IMFActivate* pActivate) {
auto front_camera = new IIDXLocalCamera("front", TDJ_CAMERA_PREFER_16_9, pActivate, s_pD3DManager, device, texture_b);
auto front_camera = new IIDXLocalCamera("front", TDJ_CAMERA_PREFER_16_9, pActivate, s_pD3DManager, device, camera_texture_b, preview_texture_b);
if (front_camera->m_initialized) {
LOCAL_CAMERA_LIST.push_back(front_camera);
} else {
@@ -469,8 +471,8 @@ namespace games::iidx {
if (result) {
log_misc(
"iidx:camhook",
"found hooks:\n hook_a 0x{:x}\n hook_b 0x{:x}\n textures 0x{:x}\n device_offset 0x{:x}",
addr_hook_a, addr_hook_b, addr_textures, addr_device_offset);
"found hooks:\n hook_a 0x{:x}\n textures 0x{:x}\n camera_manager 0x{:x}\n device_offset 0x{:x}\n afp_texture_offset 0x{:x}",
addr_hook_a, addr_textures, addr_camera_manager, addr_device_offset, addr_afp_texture_offset);
result = create_hooks();
if (result) {
init_mf_library();
@@ -493,7 +495,8 @@ namespace games::iidx {
}
bool camera_config_load() {
log_info("iidx:camhook", "loading config");
CAMERA_CONFIG_PATH =
fileutils::get_config_file_path("iidx::camhook", "spicetools_camera_control.json");
try {
// read config file
@@ -651,9 +654,9 @@ namespace games::iidx {
doc.Accept(writer);
// save to file
if (fileutils::text_write(CAMERA_CONFIG_PATH, buffer.GetString())) {
if (fileutils::write_config_file("iidx::camhook", CAMERA_CONFIG_PATH, buffer.GetString())) {
} else {
log_warning("iidx:camhook", "unable to save config file to {}", CAMERA_CONFIG_PATH.string());
log_warning("iidx:camhook", "unable to save config file");
}
return true;
+135 -25
View File
@@ -8,6 +8,7 @@
#include "games/iidx/legacy_camera.h"
#include "hooks/audio/audio.h"
#include "hooks/avshook.h"
#include "hooks/cfgmgr32hook.h"
#include "hooks/devicehook.h"
@@ -22,11 +23,15 @@
#include "misc/wintouchemu.h"
#include "misc/eamuse.h"
#include "util/detour.h"
#include "util/deferlog.h"
#include "util/fileutils.h"
#include "util/libutils.h"
#include "util/memutils.h"
#include "util/sigscan.h"
#include "util/utils.h"
#include "launcher/signal.h"
#include "external/robin_hood.h"
#include "bi2a.h"
#include "bi2x_hook.h"
@@ -48,7 +53,7 @@ namespace games::iidx {
// settings
bool FLIP_CAMS = false;
bool DISABLE_CAMS = false;
std::optional<bool> DISABLE_CAMS;
bool TDJ_CAMERA = false;
bool TDJ_CAMERA_PREFER_16_9 = true;
bool TDJ_MODE = false;
@@ -74,6 +79,26 @@ namespace games::iidx {
bool IIDXIO_LED_TICKER_READONLY = false;
std::mutex IIDX_LED_TICKER_LOCK;
tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL] = {
{ 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB, "Stage Left" },
{ 19, Lights::StageRightAvgR, Lights::StageRightAvgG, Lights::StageRightAvgB, "Stage Right" },
{ 45, Lights::CabinetLeftAvgR, Lights::CabinetLeftAvgG, Lights::CabinetLeftAvgB, "Cabinet Left" },
{ 45, Lights::CabinetRightAvgR, Lights::CabinetRightAvgG, Lights::CabinetRightAvgB, "Cabinet Right" },
{ 21, Lights::ControlPanelUnderAvgR, Lights::ControlPanelUnderAvgG, Lights::ControlPanelUnderAvgB, "Control Panel Under" },
{ 54, Lights::CeilingLeftAvgR, Lights::CeilingLeftAvgG, Lights::CeilingLeftAvgB, "Ceiling Left" },
{ 11, Lights::TitleLeftAvgR, Lights::TitleLeftAvgG, Lights::TitleLeftAvgB, "Title Left" },
{ 11, Lights::TitleRightAvgR, Lights::TitleRightAvgG, Lights::TitleRightAvgB, "Title Right" },
{ 54, Lights::CeilingRightAvgR, Lights::CeilingRightAvgG, Lights::CeilingRightAvgB, "Ceiling Right" },
{ 17, Lights::TouchPanelLeftAvgR, Lights::TouchPanelLeftAvgG, Lights::TouchPanelLeftAvgB, "Touch Panel Left" },
{ 17, Lights::TouchPanelRightAvgR, Lights::TouchPanelRightAvgG, Lights::TouchPanelRightAvgB, "Touch Panel Right" },
{ 68, Lights::SidePanelLeftInnerAvgR, Lights::SidePanelLeftInnerAvgG, Lights::SidePanelLeftInnerAvgB, "Side Panel Left Inner" },
{ 68, Lights::SidePanelLeftOuterAvgR, Lights::SidePanelLeftOuterAvgG, Lights::SidePanelLeftOuterAvgB, "Side Panel Left Outer" },
{ 61, Lights::SidePanelLeftAvgR, Lights::SidePanelLeftAvgG, Lights::SidePanelLeftAvgB, "Side Panel Left" },
{ 68, Lights::SidePanelRightOuterAvgR, Lights::SidePanelRightOuterAvgG, Lights::SidePanelRightOuterAvgB, "Side Panel Right Outer" },
{ 68, Lights::SidePanelRightInnerAvgR, Lights::SidePanelRightInnerAvgG, Lights::SidePanelRightInnerAvgB, "Side Panel Right Inner" },
{ 61, Lights::SidePanelRightAvgR, Lights::SidePanelRightAvgG, Lights::SidePanelRightAvgB, "Side Panel Right" },
};
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
if (lpSubKey != nullptr && phkResult != nullptr) {
if (hKey == HKEY_LOCAL_MACHINE &&
@@ -210,6 +235,9 @@ namespace games::iidx {
} else if (data.find(" W:touch: missing trigger:") != std::string::npos) {
out.clear();
return true;
} else if (data.find("SuperstepSound: Audio device is not available") != std::string::npos) {
deferredlogs::defer_error_messages(deferredlogs::SUPERSTEP_SOUND_ERROR_MESSAGE);
return false;
} else {
return false;
}
@@ -306,11 +334,6 @@ namespace games::iidx {
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
wintouchemu::hook_title_ends("beatmania IIDX", "main", avs::game::DLL_INSTANCE);
}
// prevent crash on TDJ mode without correct DLL
if (!GetModuleHandle("nvcuda.dll")) {
DISABLE_CAMS = true;
}
}
// insert BI2X hooks
@@ -350,7 +373,11 @@ namespace games::iidx {
}
}
apply_audio_hacks();
if (hooks::audio::ENABLED) {
apply_audio_hacks();
} else {
log_warning("iidx", "skipping audio hooks; disabled by user");
}
// ASIO device hook
RegCloseKey_orig = detour::iat_try(
@@ -367,7 +394,10 @@ namespace games::iidx {
"RegQueryValueExA", RegQueryValueExA_hook, avs::game::DLL_INSTANCE);
// check if cam hook should be enabled
if (!DISABLE_CAMS) {
if (!DISABLE_CAMS.has_value()) {
log_fatal("iidx", "assertion failure - DISABLE_CAMS not set during attach");
}
if (!DISABLE_CAMS.value()) {
init_legacy_camera_hook(FLIP_CAMS);
}
@@ -377,19 +407,31 @@ namespace games::iidx {
void IIDXGame::pre_attach() {
Game::pre_attach();
auto options = games::get_options(eamuse_get_game());
// environment variables must be set before the DLL is loaded as the VC++ runtime copies all
// environment variables at startup
if (DISABLE_CAMS) {
SetEnvironmentVariable("CONNECT_CAMERA", "0");
}
if (SCREEN_MODE.has_value()) {
log_misc("iidx", "SCREEN_MODE env var set to {}", SCREEN_MODE.value().c_str());
SetEnvironmentVariable("SCREEN_MODE", SCREEN_MODE.value().c_str());
}
// check for cab camera access for the second time (first time was in launcher.cpp)
// this time, we are inside -iidx module hook, which means the user is likely NOT on a cab
// therefore, start with cams OFF by default, and allow user to forcibly override to ON
if (!games::iidx::DISABLE_CAMS.has_value()) {
games::iidx::DISABLE_CAMS = true;
if (options->at(launcher::Options::IIDXCabCamAccess).is_active() &&
options->at(launcher::Options::IIDXCabCamAccess).value_text() == "on") {
games::iidx::DISABLE_CAMS = false;
}
if (games::iidx::DISABLE_CAMS.value()) {
log_misc("iidx", "CONNECT_CAMERA env var set to 0");
SetEnvironmentVariable("CONNECT_CAMERA", "0");
}
}
// windowed subscreen, enabled by default, unless turned off by user
auto options = games::get_options(eamuse_get_game());
if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool()) {
GRAPHICS_IIDX_WSUB = true;
}
@@ -400,7 +442,7 @@ namespace games::iidx {
// check bad model name
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("TDJ")) {
log_fatal(
log_warning(
"iidx",
"BAD MODEL NAME ERROR\n\n\n"
"!!! model name set to TDJ, this is WRONG and will break your game !!!\n"
@@ -409,7 +451,7 @@ namespace games::iidx {
"!!! please do the following instead: !!!\n"
"!!! !!!\n"
"!!! Revert your changes to XML file so it says !!!\n"
"!!! <model __type=\"str\">LDJ</model> !!!\n"
"!!! <model __type=\"str\">LDJ</model> !!!\n"
"!!! !!!\n"
"!!! In SpiceCfg, enable 'IIDX TDJ Mode' or provide -iidxtdj flag !!!\n"
"!!! in command line !!!\n"
@@ -418,6 +460,34 @@ namespace games::iidx {
"!!! !!!\n"
"!!! model name set to TDJ, this is WRONG and will break your game !!!\n\n\n"
);
log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead");
}
// check -monitor + TDJ mode
if (!GRAPHICS_WINDOWED &&
options->at(launcher::Options::DisplayAdapter).is_active() &&
TDJ_MODE) {
log_warning(
"iidx",
"\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n"
"!!! compatibility issues with the game !!!\n"
"!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n"
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
);
deferredlogs::defer_error_messages({
"-monitor option is NOT recommended when running with TDJ mode",
" due to known compatibility issues with the game:",
" * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay",
" recommended fix is to NOT use -monitor and instead set the primary",
" monitor in Windows settings before launching the game"
});
}
}
@@ -740,26 +810,32 @@ namespace games::iidx {
0);
// attempt to detect ASIO support
// <=24 : 32-bit only
// 25-26: has neither (no patch needed - WASAPI Exclusive by default)
// 27-30: has both (envvar will be respected, ASIO or WASAPI)
// 31+: only has XONAR (ASIO by default, signature patch can be used to force WASAPI - for now)
// 31-32: only has XONAR (ASIO by default, signature patch can be used to force WASAPI - for now)
// 33 : early versions only have XONAR, but later datecodes have both; SOUND_OUTPUT_DEVICE
// returned and it works again when set as env var
log_info(
"iidx",
"has_SOUND_OUTPUT_DEVICE: {}, has_XONAR_SOUND_CARD: {}",
(has_SOUND_OUTPUT_DEVICE != 0),
(has_XONAR_SOUND_CARD != 0));
if (!has_SOUND_OUTPUT_DEVICE && !has_XONAR_SOUND_CARD) {
// iidx 25-26
log_info("iidx", "This game only uses WASAPI audio engine");
return;
}
if (has_SOUND_OUTPUT_DEVICE && has_XONAR_SOUND_CARD) {
log_info("iidx", "This game accepts SOUND_OUTPUT_DEVICE environment variable");
return;
}
log_info("iidx", "This game supports ASIO but does not accept SOUND_OUTPUT_DEVICE environment variable");
if (SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() && SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi") {
// if the game doesn't accept SOUND_OUTPUT_DEVICE, patch game to force wasapi
if (!has_SOUND_OUTPUT_DEVICE &&
SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() &&
SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi") {
intptr_t result = replace_pattern(
avs::game::DLL_INSTANCE,
"FF5008E8????ECFF83780803740D",
"FF5008E8??????FF83780803740D",
"??????BB00000000EB169090????",
0, 0);
@@ -776,6 +852,40 @@ namespace games::iidx {
}
}
// patch iidx32+ for asio compatibility
// only do this if NOT wasapi (as opposed to checking if it's asio)
// the patch is only really needed for (some) non-XONAR devices but since people sometimes disguise
// other devices as a XONAR, don't check for the exact string (common ASIO workaround for INF)
if (avs::game::is_ext(2024090100, MAXINT) &&
!(SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() &&
SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi")) {
// in iidx32 final:
// ff 50 08 call QWORD PTR [rax+0x8] ; ASIO instance AddRef
// 48 8b 4b 08 mov rcx,QWORD PTR [rbx+0x8]
// 48 8b 01 mov rax,QWORD PTR [rcx]
// ff 50 08 call QWORD PTR [rax+0x8] ; ASIO instance AddRef
intptr_t result = replace_pattern(
avs::game::DLL_INSTANCE,
"FF50????????????????FF50??4533C94533C0418D51",
"????????????????????909090??????????????????",
0, 0);
if (result == 0) {
log_warning(
"iidx",
"Failed to apply ASIO compatibility fix for iidx32+. "
"Unless patches are applied, your ASIO device ({}) may hang or fail to work",
ASIO_DRIVER->c_str());
} else {
log_info(
"iidx",
"Successfully applied ASIO compatibility fix for iidx32+ using signature matching @ 0x{:x}.",
result);
}
}
#endif
}
+9 -1
View File
@@ -2,15 +2,19 @@
#include <mutex>
#include <optional>
#include <vector>
#include "games/game.h"
#include "external/robin_hood.h"
#include "util/tapeled.h"
namespace games::iidx {
// settings
extern bool FLIP_CAMS;
extern bool DISABLE_CAMS;
extern std::optional<bool> DISABLE_CAMS;
extern bool TDJ_CAMERA;
extern bool TDJ_CAMERA_PREFER_16_9;
extern std::optional<std::string> TDJ_CAMERA_OVERRIDE;
@@ -30,6 +34,10 @@ namespace games::iidx {
extern bool IIDXIO_LED_TICKER_READONLY;
extern std::mutex IIDX_LED_TICKER_LOCK;
constexpr int IIDX_TAPELED_TOTAL = 17;
// data mapping
extern tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL];
class IIDXGame : public games::Game {
public:
IIDXGame();
+2
View File
@@ -213,6 +213,8 @@ namespace games::iidx {
}
void init_legacy_camera_hook(bool flip_cams) {
log_info("iidx::cam", "initializing legacy camera hook");
should_flip_cams = flip_cams;
// camera media framework hook
+48 -20
View File
@@ -46,14 +46,17 @@ namespace games::iidx {
IMFActivate *pActivate,
IDirect3DDeviceManager9 *pD3DManager,
LPDIRECT3DDEVICE9EX device,
LPDIRECT3DTEXTURE9 *texture_target
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_texture_target(texture_target),
m_texture_original(*texture_target)
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);
@@ -85,12 +88,19 @@ namespace games::iidx {
&m_pwszSymbolicLink,
&m_cchSymbolicLink
);
if (FAILED(hr)) { goto done; }
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)) { goto done; }
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();
@@ -98,13 +108,22 @@ namespace games::iidx {
// Create an attribute store to hold initialization settings.
hr = WrappedMFCreateAttributes(&pAttributes, 2);
if (FAILED(hr)) { goto done; }
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)) { goto done; }
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)) { goto done; }
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)) {
@@ -120,11 +139,18 @@ namespace games::iidx {
pAttributes,
&m_pSourceReader
);
if (FAILED(hr)) { goto done; }
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)) { goto done; }
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();
@@ -134,7 +160,7 @@ namespace games::iidx {
m_initialized = true;
log_misc("iidx:camhook", "[{}] Initialized", m_name);
} else {
log_warning("iidx:camhook", "[{}] Failed to create camera: {}", m_name, hr);
log_warning("iidx:camhook", "[{}] Failed to create camera: {:#x}", m_name, (ULONG)hr);
}
SafeRelease(&pAttributes);
LeaveCriticalSection(&m_critsec);
@@ -165,7 +191,7 @@ namespace games::iidx {
if (FAILED(hr)) {
if (hr != MF_E_NO_MORE_TYPES) {
log_warning("iidx:camhook", "[{}] Cannot get media type {} {}", m_name, i, hr);
log_warning("iidx:camhook", "[{}] Cannot get media type {} {:#x}", m_name, i, (ULONG)hr);
}
break;
}
@@ -584,7 +610,7 @@ namespace games::iidx {
* Return values:
* S_OK: this is a "better" media type than the existing one
* S_FALSE: valid media type, but not "better"
* E_*: invalid meia type
* E_*: invalid media type
*/
HRESULT IIDXLocalCamera::TryMediaType(IMFMediaType *pType, UINT32 *pBestWidth, double *pBestFrameRate) {
HRESULT hr = S_OK;
@@ -595,13 +621,13 @@ namespace games::iidx {
hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get subtype: {}", m_name, 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: {}", m_name, hr);
log_warning("iidx:camhook", "[{}] Failed to get frame size: {:#x}", m_name, (ULONG)hr);
return hr;
}
@@ -619,7 +645,7 @@ namespace games::iidx {
(UINT32*)&frameRate.Denominator
);
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get frame rate: {}", m_name, hr);
log_warning("iidx:camhook", "[{}] Failed to get frame rate: {:#x}", m_name, (ULONG)hr);
return hr;
}
double frameRateValue = frameRate.Numerator / frameRate.Denominator;
@@ -668,7 +694,8 @@ namespace games::iidx {
if (FAILED(hr)) { goto done; }
// Make the game use this new texture as camera stream source
*m_texture_target = m_texture;
*m_camera_texture_target = m_texture;
*m_preview_texture_target = m_texture;
m_active = TRUE;
// Create texture for colour conversion
@@ -696,7 +723,7 @@ namespace games::iidx {
if (SUCCEEDED(hr)) {
log_misc("iidx:camhook", "[{}] Created texture", m_name);
} else {
log_warning("iidx:camhook", "[{}] Failed to create texture: {}", m_name, hr);
log_warning("iidx:camhook", "[{}] Failed to create texture: {:#x}", m_name, (ULONG)hr);
}
return hr;
}
@@ -785,7 +812,7 @@ namespace games::iidx {
FlushDrawCommands();
if (FAILED(hr)) {
log_warning("iidx:camhook", "Error in DrawSample {}", hr);
log_warning("iidx:camhook", "Error in DrawSample {:#x}", (ULONG)hr);
}
SafeRelease(&pCameraSurf);
LeaveCriticalSection(&m_critsec);
@@ -837,7 +864,8 @@ namespace games::iidx {
ULONG uCount = InterlockedDecrement(&m_nRefCount);
*m_texture_target = m_texture_original;
*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));
+27 -5
View File
@@ -66,6 +66,25 @@ extern std::string CAMERA_CONTROL_LABELS[];
extern std::string DRAW_MODE_LABELS[];
namespace games::iidx {
namespace Camera {
struct PlayVideoCamera {
IDirect3DTexture9** d3d9_texture(const uintptr_t offset) {
auto const afp_texture = *reinterpret_cast<uint8_t**>
(reinterpret_cast<uint8_t*>(this) + offset);
return reinterpret_cast<IDirect3DTexture9**>(afp_texture + 0x8);
}
};
struct CCameraManager2 {
using camera_pointers = struct {
PlayVideoCamera* a;
PlayVideoCamera* b;
};
void* vftbl;
camera_pointers* cameras;
};
}
class IIDXLocalCamera {
protected:
virtual ~IIDXLocalCamera() {};
@@ -98,8 +117,9 @@ namespace games::iidx {
// DirectX9 DeviceEx
LPDIRECT3DDEVICE9EX m_device;
// Address to hook camera texture onto the game
LPDIRECT3DTEXTURE9 *m_texture_target = nullptr;
// Address to hook camera textures onto the game
LPDIRECT3DTEXTURE9 *m_camera_texture_target = nullptr;
LPDIRECT3DTEXTURE9 *m_preview_texture_target = nullptr;
// Target texture (to be shown in the game)
LPDIRECT3DTEXTURE9 m_texture = nullptr;
@@ -117,8 +137,9 @@ namespace games::iidx {
LPDIRECT3DTEXTURE9 m_transformResultTexture = nullptr;
IDirect3DSurface9 *m_pTransformResultSurf = nullptr;
// Storing original texture for clean up
LPDIRECT3DTEXTURE9 m_texture_original = nullptr;
// Storing original textures for clean up
LPDIRECT3DTEXTURE9 m_camera_texture_original = nullptr;
LPDIRECT3DTEXTURE9 m_preview_texture_original = nullptr;
IAMCameraControl *m_pCameraControl = nullptr;
@@ -154,7 +175,8 @@ namespace games::iidx {
IMFActivate *pActivate,
IDirect3DDeviceManager9 *pD3DManager,
LPDIRECT3DDEVICE9EX device,
LPDIRECT3DTEXTURE9 *texture_target
LPDIRECT3DTEXTURE9 *camera_texture_target,
LPDIRECT3DTEXTURE9 *preview_texture_target
);
LPDIRECT3DTEXTURE9 GetTexture();
ULONG Release();
+2 -2
View File
@@ -266,8 +266,8 @@ namespace games::iidx::poke {
y *= rawinput::TOUCHSCREEN_RANGE_Y;
} else if (GRAPHICS_IIDX_WSUB) {
// Scale to windowed subscreen
x *= GRAPHICS_IIDX_WSUB_WIDTH;
y *= GRAPHICS_IIDX_WSUB_HEIGHT;
x *= GRAPHICS_WSUB_WIDTH;
y *= GRAPHICS_WSUB_HEIGHT;
} else if (GENERIC_SUB_WINDOW_FULLSIZE || !overlay::OVERLAY->get_active()) {
// Overlay is not present, scale to main screen
if (GRAPHICS_WINDOWED) {
+10
View File
@@ -36,6 +36,7 @@
#include "ccj/io.h"
#include "qks/io.h"
#include "mfg/io.h"
#include "pc/io.h"
namespace games {
@@ -313,6 +314,15 @@ namespace games {
buttons.insert({ mfg, mfg::get_buttons() });
buttons_help.insert({ mfg, mfg::get_buttons_help() });
file_hints[mfg].emplace_back("game/MFGClient_Data");
// Polaris Chord
const std::string pc("Polaris Chord");
games.push_back(pc);
buttons.insert({ pc, pc::get_buttons() });
buttons_help.insert({ pc, pc::get_buttons_help() });
analogs.insert({ pc, pc::get_analogs() });
lights.insert({ pc, pc::get_lights() });
file_hints[pc].emplace_back("game/svm_Data");
}
const std::vector<std::string> &get_games() {
+5 -1
View File
@@ -169,7 +169,7 @@ namespace games::jb {
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_fatal(
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"
@@ -179,6 +179,10 @@ namespace games::jb {
"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");
}
}
}
+34 -9
View File
@@ -13,10 +13,14 @@
namespace games::loveplus {
bool CAMERA_ENABLE = false;
// touch stuff
static bool TOUCH_ENABLE = false;
static bool TOUCH_ATTACHED = false;
static std::string lp_args = "-noWatchDog -noIOError -noIrda -notarget";
void touch_update() {
// check if touch enabled
@@ -28,16 +32,15 @@ namespace games::loveplus {
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(), "LovePlus")) {
wnd = FindWindowBeginsWith(avs::game::MODEL);
}
HWND wnd = FindProcessWindowBeginsWith("LovePlusAC");
// attach touch hook
if (wnd) {
log_info("loveplus", "using window handle for touch: {}", fmt::ptr(wnd));
log_info(
"loveplus",
"using window handle for touch: {} ({})",
fmt::ptr(wnd),
get_window_title(wnd));
touch_create_wnd(wnd);
} else {
log_info("loveplus", "falling back to the DirectX window handle for touch");
@@ -117,12 +120,26 @@ namespace games::loveplus {
}
static LPSTR __stdcall GetCommandLineA_hook() {
static std::string lp_args = "-win -noCamera -noWatchDog -noIOError -noIrda -notarget";
return lp_args.data();
}
static bool lp_spam_remover(void *user, const std::string &data, logger::Style style, std::string &out) {
if (data.empty() || data[0] != '[') {
return false;
}
if (data.find("> [INFO] err_code: 0, err_title: , err_str:") != std::string::npos) {
out = "";
return true;
}
return false;
}
LovePlusGame::LovePlusGame() : Game("LovePlus") {
logger::hook_add(lp_spam_remover, nullptr);
}
LovePlusGame::~LovePlusGame() {
logger::hook_remove(lp_spam_remover, this);
}
void LovePlusGame::attach() {
@@ -157,9 +174,17 @@ namespace games::loveplus {
HMODULE seteq = libutils::try_library("ad_hd_seteq_dll.dll");
detour::inline_hook((void *) SetEqualizer, libutils::try_proc(seteq, "SetEqualizer"));
// set up command line args
if (!CAMERA_ENABLE) {
lp_args += " -noCamera";
}
if (GRAPHICS_WINDOWED) {
lp_args += " -win";
}
// get command line hook
HMODULE lpac = libutils::try_library("lpac.dll");
detour::iat("GetCommandLineA", GetCommandLineA_hook, lpac);
}
void LovePlusGame::detach() {
+3
View File
@@ -4,12 +4,15 @@
namespace games::loveplus {
extern bool CAMERA_ENABLE;
// touch stuff
void touch_update();
class LovePlusGame : public games::Game {
public:
LovePlusGame();
virtual ~LovePlusGame() override;
virtual void attach() override;
virtual void detach() override;
};
+35 -2
View File
@@ -4,6 +4,7 @@
#include "avs/game.h"
#include "hooks/graphics/graphics.h"
#include "misc/eamuse.h"
#include "misc/wintouchemu.h"
#include "touch/touch.h"
#include "util/detour.h"
#include "util/libutils.h"
@@ -32,6 +33,8 @@ namespace games::mfc {
static uint8_t CARD_TYPE = 0;
static uint8_t CARD_UID[8];
bool HG_MODE = false;
typedef int (__cdecl *inifile_param_num_t)(const char *, int *);
static inifile_param_num_t inifile_param_num_real;
@@ -50,8 +53,10 @@ namespace games::mfc {
static void __cdecl touch_init(int width, int height) {
log_info("mfc", "call touch_init(width: {}, height: {})", width, height);
// attach touch module
if (!TOUCH_ATTACHED) {
if (HG_MODE) {
wintouchemu::ADD_TOUCH_FLAG_PRIMARY = true;
wintouchemu::hook("MFC9", avs::game::DLL_INSTANCE);
} else if (!TOUCH_ATTACHED) { // attach touch module
// store touch size specification
TOUCH_MAX_X = width;
@@ -288,6 +293,21 @@ namespace games::mfc {
MFCGame::MFCGame() : Game("Mahjong Fight Club") {
}
static bool spam_remover(void *user, const std::string &data, logger::Style style, std::string &out) {
if (data.empty() || data[0] != '[') {
return false;
}
if (data.find("W:mutex: lock: mid 00000000 != AVS_DTYPE_MUTEX") != std::string::npos) {
out = "";
return true;
}
if (data.find("W:mutex: unlock: mid 00000000 != AVS_DTYPE_MUTEX") != std::string::npos) {
out = "";
return true;
}
return false;
}
void MFCGame::attach() {
Game::attach();
@@ -308,6 +328,17 @@ namespace games::mfc {
auto allinone_module = libutils::try_module("allinone.dll");
auto system_module = libutils::try_module("system.dll");
// Mahjong Fight Club - 2025?
// they removed allinone.dll and moved i/o into system.dll
if (allinone_module == nullptr) {
log_misc("mfc", "using system.dll instead of allinone.dll for i/o hooks");
allinone_module = system_module;
if (avs::game::SPEC[0] == 'F') {
HG_MODE = true;
}
}
// network fix
detour::iat_try("?nic_dhcp_maybe_exist@@YAEXZ", nic_dhcp_maybe_exist, system_module);
@@ -378,5 +409,7 @@ namespace games::mfc {
detour::inline_hook((void *) mouse_utl_step, libutils::try_proc(
allinone_module, "?mouse_utl_step@@YAXXZ"));
}
logger::hook_add(spam_remover, nullptr);
}
}
+2
View File
@@ -4,6 +4,8 @@
namespace games::mfc {
extern bool HG_MODE;
struct joystick_state {
bool up = false;
bool down = false;
+1 -1
View File
@@ -28,7 +28,7 @@ namespace games::mfg {
SetEnvironmentVariableA("VFG_CABINET_TYPE", MFG_CABINET_TYPE.c_str());
// add card reader
portName = MFG_CABINET_TYPE == "UKS" ? L"\\\\.\\COM1" : L"\\\\.\\COM3";
portName = (MFG_CABINET_TYPE == "UKS") ? std::wstring(L"\\\\.\\COM1") : std::wstring(L"\\\\.\\COM3");
acioHandle = new acioemu::ACIOHandle(portName.c_str(), 1);
devicehook_init_trampoline();
devicehook_add(acioHandle);
+1 -1
View File
@@ -94,7 +94,7 @@ namespace games::otoca {
bool kb_insert_press = false;
// eamio keypress
kb_insert_press |= eamuse_get_keypad_state(0) & (1 << EAM_IO_INSERT);
kb_insert_press |= static_cast<bool>(eamuse_get_keypad_state(0) & (1 << EAM_IO_INSERT));
// check for card
if (CARD_DATA == nullptr && (eamuse_card_insert_consume(1, 0) || kb_insert_press)) {
+739
View File
@@ -0,0 +1,739 @@
#include "bi2x_hook.h"
#include "util/execexe.h"
#include "util/logging.h"
#include "rawinput/rawinput.h"
#include "misc/eamuse.h"
#include "games/io.h"
#include "io.h"
#include "util/tapeled.h"
#include "pc.h"
#include "util/utils.h"
namespace games::pc {
bool PC_KNOB_MODE = false;
constexpr float RELATIVE_DECAY = 0.02f;
struct FADER_RELATIVE {
// ranges from 0.f to 1.f
float previous_raw = 0.5f;
// ranges from -1.f to +1.f
float effective_value = 0.f;
};
FADER_RELATIVE fader_l_relative;
FADER_RELATIVE fader_r_relative;
float calculate_analog_raw_delta(float old_value, float new_value) {
// assumed that values are between 0.f and 1.f
float delta = 0.f;
if ((old_value < 0.25f) && (0.75f < new_value)) {
// wrapped around: going left
delta = -((1.f - new_value) + old_value);
} else if ((new_value < 0.25f) && (0.75f < old_value)) {
// wrapped around: going right
delta = (1.f - old_value) + new_value;
} else {
delta = new_value - old_value;
}
return delta;
}
float apply_decay(float val) {
// return to center over time
if (RELATIVE_DECAY < val) {
return val - RELATIVE_DECAY;
} else if (val < -RELATIVE_DECAY) {
return val + RELATIVE_DECAY;
} else {
return 0.f;
}
}
float get_fader_knob_mode_value(float raw_analog, FADER_RELATIVE &fader) {
// add new value...
const float delta = calculate_analog_raw_delta(fader.previous_raw, raw_analog);
fader.effective_value += (delta * 20.f);
fader.previous_raw = raw_analog;
// capture the result after adding new input value
fader.effective_value = CLAMP(fader.effective_value, -1.f, 1.f);
const float result = fader.effective_value;
// post-process for next iteration: apply decay (return to center over time)
fader.effective_value = apply_decay(fader.effective_value);
return result;
}
/*
* class definitions
*/
struct AIO_SCI_COMM {
};
struct AIO_NMGR_IOB2 {
};
struct AIO_IOB2_BI2X_AC1 {
};
struct AIO_IOB2_BI2X_AC1__SETTING {
};
struct AIO_IOB2_BI2X_WRFIRM {
};
struct AIO_IOB2_BI2X_AC1__INPUT
{
uint8_t DevIoCounter;
uint8_t bExIoAErr;
uint8_t bExIoBErr;
uint8_t bPcPowerOn;
uint8_t bPcPowerCheck;
uint8_t bCoin1Jam;
uint8_t bCoin2Jam;
uint8_t bCoin3Jam;
uint8_t bCoin4Jam;
uint8_t Coin1Count;
uint8_t Coin2Count;
uint8_t Coin3Count;
uint8_t Coin4Count;
uint16_t AnalogCh1;
uint16_t AnalogCh2;
uint16_t AnalogCh3;
uint16_t AnalogCh4;
uint8_t CN8_8;
uint8_t CN8_9;
uint8_t CN8_10;
uint8_t CN9_8;
uint8_t CN9_9;
uint8_t CN9_10;
uint8_t CN11_11;
uint8_t CN11_12;
uint8_t CN11_13;
uint8_t CN11_14;
uint8_t CN11_15;
uint8_t CN11_16;
uint8_t CN11_17;
uint8_t CN11_18;
uint8_t CN11_19;
uint8_t CN11_20;
uint8_t CN12_11;
uint8_t CN12_12;
uint8_t CN12_13;
uint8_t CN12_14;
uint8_t CN12_15;
uint8_t CN12_16;
uint8_t CN12_17;
uint8_t CN12_18;
uint8_t CN12_19;
uint8_t CN12_20;
uint8_t CN12_21;
uint8_t CN12_22;
uint8_t CN12_23;
uint8_t CN12_24;
uint8_t CN15_3;
uint8_t CN15_4;
uint8_t CN15_5;
uint8_t CN15_6;
uint8_t CN15_7;
uint8_t CN15_8;
uint8_t CN15_9;
uint8_t CN15_10;
uint8_t CN15_11;
uint8_t CN15_12;
uint8_t CN15_13;
uint8_t CN15_14;
uint8_t CN15_15;
uint8_t CN15_16;
uint8_t CN15_17;
uint8_t CN15_18;
uint8_t CN15_19;
uint8_t CN15_20;
uint8_t CN19_8;
uint8_t CN19_9;
uint8_t CN19_10;
uint8_t CN19_11;
uint8_t CN19_12;
uint8_t CN19_13;
uint8_t CN19_14;
uint8_t CN19_15;
};
struct AIO_IOB2_BI2X_AC1__INPUTDATA {
uint8_t data[247];
};
struct AIO_IOB2_BI2X_AC1__OUTPUTDATA {
uint8_t data[48];
};
struct AIO_IOB2_BI2X_AC1__ICNPIN {
uint16_t Ain[4];
uint64_t CnPin;
};
struct AIO_IOB2_BI2X_AC1__DEVSTATUS {
uint8_t InputCounter;
uint8_t OutputCounter;
uint8_t IoResetCounter;
uint8_t TapeLedCounter;
uint8_t TapeLedRate[8];
AIO_IOB2_BI2X_AC1__INPUT Input;
AIO_IOB2_BI2X_AC1__INPUTDATA InputData;
AIO_IOB2_BI2X_AC1__OUTPUTDATA OutputData;
AIO_IOB2_BI2X_AC1__ICNPIN ICnPinHist[20];
};
/*
* typedefs
*/
// libaio-iob2_video.dll
typedef AIO_IOB2_BI2X_AC1* (__fastcall *aioIob2Bi2xAC1_Create_t)(AIO_NMGR_IOB2 *i_pNodeMgr, uint32_t i_DevId,
AIO_IOB2_BI2X_AC1__SETTING *i_Setting);
typedef void (__fastcall *aioIob2Bi2xAC1_GetDeviceStatus_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl,
AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus);
typedef void (__fastcall *aioIob2Bi2xAC1_SetWatchDogTimer_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint8_t i_Count);
typedef void (__fastcall *aioIob2Bi2xAC1_AddCounter_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Counter,
uint32_t i_Count);
typedef void (__fastcall *aioIob2Bi2xAC1_SetOutputData_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_CnPin,
uint8_t i_Data);
typedef void (__fastcall *aioIob2Bi2xAC1_SetTapeLedDataPart_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh,
uint32_t i_Offset, uint8_t *i_pData,
uint32_t i_cntTapeLed, bool i_bReverse);
typedef void (__fastcall *aioIob2Bi2x_SetTapeLedDataGroup_t)(AIO_IOB2_BI2X_AC1* i_pNodeCtl, uint32_t i_bfGroup);
typedef void (__fastcall *aioIob2Bi2x_SetTapeLedDataLimit_t)(AIO_IOB2_BI2X_AC1* i_pNodeCtl, uint32_t i_Channel,
uint8_t i_Scale, uint8_t i_Limit);
typedef AIO_IOB2_BI2X_WRFIRM *(__fastcall *aioIob2Bi2x_CreateWriteFirmContext_t)(uint32_t i_SerialNumber,
uint32_t i_bfIob);
typedef AIO_SCI_COMM* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint32_t i_SerialNumber);
typedef void (__fastcall *aioIob2Bi2x_DestroyWriteFirmContext_t)(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm);
typedef int32_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm);
typedef bool (__fastcall *aioIob2Bi2x_WriteFirmIsCompleted_t)(int32_t i_State);
typedef bool (__fastcall *aioIob2Bi2x_WriteFirmIsError_t)(int32_t i_State);
// libaio-iob.dll
typedef AIO_NMGR_IOB2 *(__fastcall *aioNMgrIob2_Create_t)(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode);
typedef void (__fastcall *aioNMgrIob_BeginManage_t)(AIO_NMGR_IOB2 *i_pNodeMgr);
// libaio.dll
typedef void (__fastcall *aioSci_Destroy_t)(AIO_SCI_COMM *i_pNodeMgr);
typedef void (__fastcall *aioNodeMgr_Destroy_t)(AIO_NMGR_IOB2 *i_pNodeMgr);
typedef int32_t (__fastcall *aioNodeMgr_GetState_t)(AIO_NMGR_IOB2 *i_pNodeMgr);
typedef bool (__fastcall *aioNodeMgr_IsReady_t)(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State);
typedef bool (__fastcall *aioNodeMgr_IsError_t)(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State);
typedef void (__fastcall *aioNodeCtl_Destroy_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl);
typedef int32_t (__fastcall *aioNodeCtl_GetState_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl);
typedef bool (__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, int32_t i_State);
typedef bool (__fastcall *aioNodeCtl_IsError_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, int32_t i_State);
/*
* function pointers
*/
// libaio-iob2_video.dll
static aioIob2Bi2xAC1_Create_t aioIob2Bi2xAC1_Create_orig = nullptr;
static aioIob2Bi2xAC1_GetDeviceStatus_t aioIob2Bi2xAC1_GetDeviceStatus_orig = nullptr;
static aioIob2Bi2xAC1_SetWatchDogTimer_t aioIob2Bi2xAC1_SetWatchDogTimer_orig = nullptr;
static aioIob2Bi2xAC1_AddCounter_t aioIob2Bi2xAC1_AddCounter_orig = nullptr;
static aioIob2Bi2xAC1_SetOutputData_t aioIob2Bi2xAC1_SetOutputData_orig = nullptr;
static aioIob2Bi2xAC1_SetTapeLedDataPart_t aioIob2Bi2xAC1_SetTapeLedDataPart_orig = nullptr;
static aioIob2Bi2x_SetTapeLedDataGroup_t aioIob2Bi2x_SetTapeLedDataGroup_orig = nullptr;
static aioIob2Bi2x_SetTapeLedDataLimit_t aioIob2Bi2x_SetTapeLedDataLimit_orig = nullptr;
static aioIob2Bi2x_OpenSciUsbCdc_t aioIob2Bi2x_OpenSciUsbCdc_orig = nullptr;
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
static aioIob2Bi2x_DestroyWriteFirmContext_t aioIob2Bi2x_DestroyWriteFirmContext_orig = nullptr;
static aioIob2Bi2x_WriteFirmGetState_t aioIob2Bi2x_WriteFirmGetState_orig = nullptr;
static aioIob2Bi2x_WriteFirmIsCompleted_t aioIob2Bi2x_WriteFirmIsCompleted_orig = nullptr;
static aioIob2Bi2x_WriteFirmIsError_t aioIob2Bi2x_WriteFirmIsError_orig = nullptr;
// libaio-iob.dll
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
static aioNMgrIob_BeginManage_t aioNMgrIob_BeginManage_orig = nullptr;
// libaio.dll
static aioSci_Destroy_t aioSci_Destroy_orig = nullptr;
static aioNodeMgr_Destroy_t aioNodeMgr_Destroy_orig = nullptr;
static aioNodeMgr_GetState_t aioNodeMgr_GetState_orig = nullptr;
static aioNodeMgr_IsReady_t aioNodeMgr_IsReady_orig = nullptr;
static aioNodeMgr_IsError_t aioNodeMgr_IsError_orig = nullptr;
static aioNodeCtl_Destroy_t aioNodeCtl_Destroy_orig = nullptr;
static aioNodeCtl_GetState_t aioNodeCtl_GetState_orig = nullptr;
static aioNodeCtl_IsReady_t aioNodeCtl_IsReady_orig = nullptr;
static aioNodeCtl_IsError_t aioNodeCtl_IsError_orig = nullptr;
/*
* variables
*/
static AIO_IOB2_BI2X_AC1 *aioIob2Bi2xAc1;
static AIO_SCI_COMM *aioSciComm;
static AIO_NMGR_IOB2 *aioNmgrIob2;
static AIO_IOB2_BI2X_WRFIRM *aioIob2Bi2xWrfirm;
/*
* implementations
*/
// libaio-iob2_video.dll
static AIO_IOB2_BI2X_AC1* __fastcall aioIob2Bi2xAC1_Create(AIO_NMGR_IOB2 *i_pNodeMgr, uint32_t i_DevId,
AIO_IOB2_BI2X_AC1__SETTING *i_Setting) {
if (i_pNodeMgr == aioNmgrIob2) {
log_info("bi2x_hook", "aioIob2Bi2xAC1_Create hook hit");
aioIob2Bi2xAc1 = new AIO_IOB2_BI2X_AC1;
return aioIob2Bi2xAc1;
} else {
return aioIob2Bi2xAC1_Create_orig(i_pNodeMgr, i_DevId, i_Setting);
}
}
void __fastcall aioIob2Bi2xAC1_GetDeviceStatus(AIO_IOB2_BI2X_AC1 *i_pNodeCtl,
AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus) {
RI_MGR->devices_flush_output();
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2xAC1_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus);
}
memset(o_DevStatus, 0x00, sizeof(AIO_IOB2_BI2X_AC1__DEVSTATUS));
auto &buttons = get_buttons();
o_DevStatus->Input.CN8_8 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]) ? 0 : 0xFF;
o_DevStatus->Input.CN8_9 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]) ? 0 : 0xFF;
o_DevStatus->Input.CN8_10 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]) ? 0 : 0xFF;
o_DevStatus->Input.CN15_10 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone]) ? 0 : 0xFF;
o_DevStatus->Input.CN15_12 = 0xFF; // Recorder off; present in I/O test menu but unused in game
o_DevStatus->Input.CN12_11 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button1]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_12 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button2]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_13 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button3]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_14 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button4]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_15 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button5]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_16 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button6]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_17 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button7]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_18 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button8]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_19 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button9]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_20 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button10]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_21 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button11]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_22 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button12]) ? 0 : 0xFF;
auto &analogs = get_analogs();
// FADER-L
float val = 0.f;
if (analogs[Analogs::FaderL].isSet()) {
if (PC_KNOB_MODE) {
val = get_fader_knob_mode_value(
GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderL]),
fader_l_relative);
} else {
val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderL]) - 0.5f) * 2;
}
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left]) &&
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) {
val = 0.f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left])) {
val = -1.0f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) {
val = +1.0f;
}
o_DevStatus->Input.CN15_7 = (val < 0.2f) ? 0xFF : 0;
o_DevStatus->Input.CN15_6 = (val > -0.85f && val < 0.35f) ? 0xFF : 0;
o_DevStatus->Input.CN15_5 = (val > -0.6f && val < 0.6f) ? 0xFF : 0;
o_DevStatus->Input.CN15_4 = (val > -0.35f && val < 0.85f) ? 0xFF : 0;
o_DevStatus->Input.CN15_3 = (val > -0.2f) ? 0xFF : 0;
// FADER-R
val = 0.f;
if (analogs[Analogs::FaderR].isSet()) {
if (PC_KNOB_MODE) {
val = get_fader_knob_mode_value(
GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderR]),
fader_r_relative);
} else {
val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderR]) - 0.5f) * 2;
}
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left]) &&
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) {
val = 0.f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left])) {
val = -1.0f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) {
val = +1.0f;
}
o_DevStatus->Input.CN11_20 = (val < 0.2f) ? 0xFF : 0;
o_DevStatus->Input.CN11_19 = (val > -0.85f && val < 0.35f) ? 0xFF : 0;
o_DevStatus->Input.CN9_10 = (val > -0.6f && val < 0.6f) ? 0xFF : 0;
o_DevStatus->Input.CN9_9 = (val > -0.35f && val < 0.85f) ? 0xFF : 0;
o_DevStatus->Input.CN9_8 = (val > -0.2f) ? 0xFF : 0;
// coin
o_DevStatus->Input.Coin1Count = eamuse_coin_get_stock();
}
void __fastcall aioIob2Bi2xAC1_SetWatchDogTimer(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint8_t i_Count) {
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2xAC1_SetWatchDogTimer_orig(i_pNodeCtl, i_Count);
}
}
void __fastcall aioIob2Bi2xAC1_AddCounter(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Counter, uint32_t i_Count) {
if (i_pNodeCtl == aioIob2Bi2xAc1 && i_Count == 0) {
eamuse_coin_set_stock((uint16_t) i_Count);
} else {
return aioIob2Bi2xAC1_AddCounter_orig(i_pNodeCtl, i_Counter, i_Count);
}
}
void __fastcall aioIob2Bi2xAC1_SetOutputData(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_CnPin, uint8_t i_Data) {
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2xAC1_SetOutputData_orig(i_pNodeCtl, i_CnPin, i_Data);
}
if (i_CnPin == 0x1) {
eamuse_coin_set_block(i_Data == 0xFF);
return;
}
auto &lights = get_lights();
if (i_CnPin == 0x11) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::IC_READER_R), i_Data / 255.0f);
} else if (i_CnPin == 0x12) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::IC_READER_G), i_Data / 255.0f);
} else if (i_CnPin == 0x13) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::IC_READER_B), i_Data / 255.0f);
}
}
struct PolarisChordLight {
int data_index;
games::pc::Lights::pc_lights_t light;
PolarisChordLight(
int data_index, games::pc::Lights::pc_lights_t light) :
data_index(data_index), light(light) {}
};
static void set_led_value(games::pc::Lights::pc_lights_t light, uint8_t value) {
auto &lights = games::pc::get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights.at(light), value / 255.f);
}
void __fastcall aioIob2Bi2xAC1_SetTapeLedDataPart(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh,
uint32_t i_Offset, uint8_t *i_pData,
uint32_t i_cntTapeLed, bool i_bReverse) {
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2xAC1_SetTapeLedDataPart_orig(i_pNodeCtl, i_TapeLedCh, i_Offset, i_pData, i_cntTapeLed, i_bReverse);
}
// log_info(
// "pc",
// "lamp [{}] [{}] = {},{},{}, cnt={}",
// i_TapeLedCh,
// i_Offset,
// i_pData[0], i_pData[1], i_pData[2],
// i_cntTapeLed);
// [channel 0]
// these are button lamps; there are 12 buttons (columns) with this layout:
//
// 0 5 8 11 14 16 | 18 20 22 25 28 31
// 1 6 9 12 15 17 | 19 21 23 26 29 32
// 2 7 10 13 | 24 27 30 33
// 3 | 34
// 4 | 35
//
// 36 RGB lamps * 3 = 108 LEDs
// each value ranges from [0, 255]
// (we only care about the top row)
static PolarisChordLight button_lights_mapping[] = {
{(0 * 3) + 0, games::pc::Lights::pc_lights_t::Button1_R},
{(0 * 3) + 1, games::pc::Lights::pc_lights_t::Button1_G},
{(0 * 3) + 2, games::pc::Lights::pc_lights_t::Button1_B},
{(5 * 3) + 0, games::pc::Lights::pc_lights_t::Button2_R},
{(5 * 3) + 1, games::pc::Lights::pc_lights_t::Button2_G},
{(5 * 3) + 2, games::pc::Lights::pc_lights_t::Button2_B},
{(8 * 3) + 0, games::pc::Lights::pc_lights_t::Button3_R},
{(8 * 3) + 1, games::pc::Lights::pc_lights_t::Button3_G},
{(8 * 3) + 2, games::pc::Lights::pc_lights_t::Button3_B},
{(11 * 3) + 0, games::pc::Lights::pc_lights_t::Button4_R},
{(11 * 3) + 1, games::pc::Lights::pc_lights_t::Button4_G},
{(11 * 3) + 2, games::pc::Lights::pc_lights_t::Button4_B},
{(14 * 3) + 0, games::pc::Lights::pc_lights_t::Button5_R},
{(14 * 3) + 1, games::pc::Lights::pc_lights_t::Button5_G},
{(14 * 3) + 2, games::pc::Lights::pc_lights_t::Button5_B},
{(16 * 3) + 0, games::pc::Lights::pc_lights_t::Button6_R},
{(16 * 3) + 1, games::pc::Lights::pc_lights_t::Button6_G},
{(16 * 3) + 2, games::pc::Lights::pc_lights_t::Button6_B},
{(18 * 3) + 0, games::pc::Lights::pc_lights_t::Button7_R},
{(18 * 3) + 1, games::pc::Lights::pc_lights_t::Button7_G},
{(18 * 3) + 2, games::pc::Lights::pc_lights_t::Button7_B},
{(20 * 3) + 0, games::pc::Lights::pc_lights_t::Button8_R},
{(20 * 3) + 1, games::pc::Lights::pc_lights_t::Button8_G},
{(20 * 3) + 2, games::pc::Lights::pc_lights_t::Button8_B},
{(22 * 3) + 0, games::pc::Lights::pc_lights_t::Button9_R},
{(22 * 3) + 1, games::pc::Lights::pc_lights_t::Button9_G},
{(22 * 3) + 2, games::pc::Lights::pc_lights_t::Button9_B},
{(25 * 3) + 0, games::pc::Lights::pc_lights_t::Button10_R},
{(25 * 3) + 1, games::pc::Lights::pc_lights_t::Button10_G},
{(25 * 3) + 2, games::pc::Lights::pc_lights_t::Button10_B},
{(28 * 3) + 0, games::pc::Lights::pc_lights_t::Button11_R},
{(28 * 3) + 1, games::pc::Lights::pc_lights_t::Button11_G},
{(28 * 3) + 2, games::pc::Lights::pc_lights_t::Button11_B},
{(31 * 3) + 0, games::pc::Lights::pc_lights_t::Button12_R},
{(31 * 3) + 1, games::pc::Lights::pc_lights_t::Button12_G},
{(31 * 3) + 2, games::pc::Lights::pc_lights_t::Button12_B},
};
if (i_TapeLedCh == 0 && i_Offset == 0 && i_cntTapeLed == 108) {
for (const auto &map : button_lights_mapping) {
set_led_value(map.light, i_pData[map.data_index]);
}
}
}
void __fastcall aioIob2Bi2x_SetTapeLedDataGroup(AIO_IOB2_BI2X_AC1* i_pNodeCtl, uint32_t i_bfGroup) {
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2x_SetTapeLedDataGroup_orig(i_pNodeCtl, i_bfGroup);
}
}
void __fastcall aioIob2Bi2x_SetTapeLedDataLimit(AIO_IOB2_BI2X_AC1* i_pNodeCtl, uint32_t i_Channel,
uint8_t i_Scale, uint8_t i_Limit) {
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2x_SetTapeLedDataLimit_orig(i_pNodeCtl, i_Channel, i_Scale, i_Limit);
}
}
static AIO_SCI_COMM *__fastcall aioIob2Bi2x_OpenSciUsbCdc(uint32_t i_SerialNumber) {
aioSciComm = new AIO_SCI_COMM;
return aioSciComm;
}
static AIO_IOB2_BI2X_WRFIRM *__fastcall aioIob2Bi2x_CreateWriteFirmContext(
uint32_t i_SerialNumber, uint32_t i_bfIob) {
aioIob2Bi2xWrfirm = new AIO_IOB2_BI2X_WRFIRM;
return aioIob2Bi2xWrfirm;
}
static void __fastcall aioIob2Bi2x_DestroyWriteFirmContext(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm) {
if (i_pWrFirm == aioIob2Bi2xWrfirm) {
delete aioIob2Bi2xWrfirm;
aioIob2Bi2xWrfirm = nullptr;
} else {
return aioIob2Bi2x_DestroyWriteFirmContext_orig(i_pWrFirm);
}
}
static int32_t __fastcall aioIob2Bi2x_WriteFirmGetState(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm) {
if (i_pWrFirm == aioIob2Bi2xWrfirm) {
return 8;
} else {
return aioIob2Bi2x_WriteFirmGetState_orig(i_pWrFirm);
}
}
static bool __fastcall aioIob2Bi2x_WriteFirmIsCompleted(int32_t i_State) {
if (aioIob2Bi2xWrfirm != nullptr) {
return true;
} else {
return aioIob2Bi2x_WriteFirmIsCompleted_orig(i_State);
}
}
static bool __fastcall aioIob2Bi2x_WriteFirmIsError(int32_t i_State) {
if (aioIob2Bi2xWrfirm != nullptr) {
return false;
} else {
return aioIob2Bi2x_WriteFirmIsError_orig(i_State);
}
}
// libaio-iob.dll
static AIO_NMGR_IOB2 *__fastcall aioNMgrIob2_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
if (i_pSci == aioSciComm) {
aioNmgrIob2 = new AIO_NMGR_IOB2;
return aioNmgrIob2;
} else {
return aioNMgrIob2_Create_orig(i_pSci, i_bfMode);
}
}
static void __fastcall aioNMgrIob_BeginManage(AIO_NMGR_IOB2 *i_pNodeMgr) {
if (i_pNodeMgr != aioNmgrIob2) {
return aioNMgrIob_BeginManage_orig(i_pNodeMgr);
}
}
// libaio.dll
static void __fastcall aioSci_Destroy(AIO_SCI_COMM *i_pNodeMgr) {
if (i_pNodeMgr == aioSciComm) {
delete aioSciComm;
} else {
return aioSci_Destroy_orig(i_pNodeMgr);
}
}
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB2 *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob2) {
delete aioNmgrIob2;
aioNmgrIob2 = nullptr;
} else {
return aioNodeMgr_Destroy_orig(i_pNodeMgr);
}
}
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB2 *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob2) {
return 1;
} else {
return aioNodeMgr_GetState_orig(i_pNodeMgr);
}
}
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob2) {
return true;
} else {
return aioNodeMgr_IsReady_orig(i_pNodeMgr, i_State);
}
}
static bool __fastcall aioNodeMgr_IsError(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob2) {
return false;
} else {
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
}
}
static void __fastcall aioNodeCtl_Destroy(AIO_IOB2_BI2X_AC1 *i_pNodeCtl) {
if (i_pNodeCtl == aioIob2Bi2xAc1) {
delete aioIob2Bi2xAc1;
aioIob2Bi2xAc1 = nullptr;
} else {
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
}
}
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB2_BI2X_AC1 *i_pNodeCtl) {
if (i_pNodeCtl == aioIob2Bi2xAc1) {
return 1;
} else {
return aioNodeCtl_GetState_orig(i_pNodeCtl);
}
}
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIob2Bi2xAc1) {
return true;
} else {
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
}
}
static bool __fastcall aioNodeCtl_IsError(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIob2Bi2xAc1) {
return false;
} else {
return aioNodeCtl_IsError_orig(i_pNodeCtl, i_State);
}
}
void bi2x_hook_init() {
// avoid double init
static bool initialized = false;
if (initialized) {
return;
} else {
initialized = true;
}
// announce
log_info("bi2x_hook", "init");
// libaio-iob2_video.dll
const auto libaioIob2VideoDll = "libaio-iob2_video.dll";
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_Create",
aioIob2Bi2xAC1_Create, &aioIob2Bi2xAC1_Create_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_GetDeviceStatus",
aioIob2Bi2xAC1_GetDeviceStatus, &aioIob2Bi2xAC1_GetDeviceStatus_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetWatchDogTimer",
aioIob2Bi2xAC1_SetWatchDogTimer, &aioIob2Bi2xAC1_SetWatchDogTimer_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_AddCounter",
aioIob2Bi2xAC1_AddCounter, &aioIob2Bi2xAC1_AddCounter_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetOutputData",
aioIob2Bi2xAC1_SetOutputData, &aioIob2Bi2xAC1_SetOutputData_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetTapeLedDataPart",
aioIob2Bi2xAC1_SetTapeLedDataPart, &aioIob2Bi2xAC1_SetTapeLedDataPart_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_SetTapeLedDataGroup",
aioIob2Bi2x_SetTapeLedDataGroup, &aioIob2Bi2x_SetTapeLedDataGroup_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_SetTapeLedDataLimit",
aioIob2Bi2x_SetTapeLedDataLimit, &aioIob2Bi2x_SetTapeLedDataLimit_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_OpenSciUsbCdc",
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_CreateWriteFirmContext",
aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_DestroyWriteFirmContext",
aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_WriteFirmGetState",
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_WriteFirmIsCompleted",
aioIob2Bi2x_WriteFirmIsCompleted, &aioIob2Bi2x_WriteFirmIsCompleted_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_WriteFirmIsError",
aioIob2Bi2x_WriteFirmIsError, &aioIob2Bi2x_WriteFirmIsError_orig);
// libaio-iob.dll
const auto libaioIobDll = "libaio-iob.dll";
execexe::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
execexe::trampoline_try(libaioIobDll, "aioNMgrIob_BeginManage",
aioNMgrIob_BeginManage, &aioNMgrIob_BeginManage_orig);
// libaio.dll
const auto libaioDll = "libaio.dll";
execexe::trampoline_try(libaioDll, "aioSci_Destroy",
aioSci_Destroy, &aioSci_Destroy_orig);
execexe::trampoline_try(libaioDll, "aioNodeMgr_Destroy",
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
execexe::trampoline_try(libaioDll, "aioNodeMgr_GetState",
aioNodeMgr_GetState, &aioNodeMgr_GetState_orig);
execexe::trampoline_try(libaioDll, "aioNodeMgr_IsReady",
aioNodeMgr_IsReady, &aioNodeMgr_IsReady_orig);
execexe::trampoline_try(libaioDll, "aioNodeMgr_IsError",
aioNodeMgr_IsError, &aioNodeMgr_IsError_orig);
execexe::trampoline_try(libaioDll, "aioNodeCtl_Destroy",
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
execexe::trampoline_try(libaioDll, "aioNodeCtl_GetState",
aioNodeCtl_GetState, &aioNodeCtl_GetState_orig);
execexe::trampoline_try(libaioDll, "aioNodeCtl_IsReady",
aioNodeCtl_IsReady, &aioNodeCtl_IsReady_orig);
execexe::trampoline_try(libaioDll, "aioNodeCtl_IsError",
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
}
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
namespace games::pc {
void bi2x_hook_init();
}
+127
View File
@@ -0,0 +1,127 @@
#include "io.h"
std::vector<Button> &games::pc::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Polaris Chord");
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",
"Button 10",
"Button 11",
"Button 12",
"Fader-L Left",
"Fader-L Right",
"Fader-R Left",
"Fader-R Right",
"Headphone"
);
}
return buttons;
}
std::string games::pc::get_buttons_help() {
// keep to max 100 characters wide
return
" FADER-L FADER-R \n"
"\n"
" B1 B2 B3 ... B11 B12\n"
"\n"
" Most menu interactions are on the touch screen; you can use your mouse.\n"
;
}
std::vector<Analog> &games::pc::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Polaris Chord");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Fader-L",
"Fader-R"
);
}
return analogs;
}
std::vector<Light> &games::pc::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Polaris Chord");
GameAPI::Lights::sortLights(
&lights,
"IC Reader R",
"IC Reader G",
"IC Reader B",
"Button 1 R",
"Button 1 G",
"Button 1 B",
"Button 2 R",
"Button 2 G",
"Button 2 B",
"Button 3 R",
"Button 3 G",
"Button 3 B",
"Button 4 R",
"Button 4 G",
"Button 4 B",
"Button 5 R",
"Button 5 G",
"Button 5 B",
"Button 6 R",
"Button 6 G",
"Button 6 B",
"Button 7 R",
"Button 7 G",
"Button 7 B",
"Button 8 R",
"Button 8 G",
"Button 8 B",
"Button 9 R",
"Button 9 G",
"Button 9 B",
"Button 10 R",
"Button 10 G",
"Button 10 B",
"Button 11 R",
"Button 11 G",
"Button 11 B",
"Button 12 R",
"Button 12 G",
"Button 12 B"
);
}
return lights;
}
+101
View File
@@ -0,0 +1,101 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::pc {
namespace Buttons {
enum {
Service,
Test,
CoinMech,
Button1,
Button2,
Button3,
Button4,
Button5,
Button6,
Button7,
Button8,
Button9,
Button10,
Button11,
Button12,
FaderL_Left,
FaderL_Right,
FaderR_Left,
FaderR_Right,
Headphone
};
}
namespace Analogs {
enum {
FaderL,
FaderR,
};
}
namespace Lights {
typedef enum {
IC_READER_R,
IC_READER_G,
IC_READER_B,
Button1_R,
Button1_G,
Button1_B,
Button2_R,
Button2_G,
Button2_B,
Button3_R,
Button3_G,
Button3_B,
Button4_R,
Button4_G,
Button4_B,
Button5_R,
Button5_G,
Button5_B,
Button6_R,
Button6_G,
Button6_B,
Button7_R,
Button7_G,
Button7_B,
Button8_R,
Button8_G,
Button8_B,
Button9_R,
Button9_G,
Button9_B,
Button10_R,
Button10_G,
Button10_B,
Button11_R,
Button11_G,
Button11_B,
Button12_R,
Button12_G,
Button12_B
} pc_lights_t;
}
// getters
std::vector<Button> &get_buttons();
std::string get_buttons_help();
std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}
+110
View File
@@ -0,0 +1,110 @@
#include "pc.h"
#include <format>
#include "bi2x_hook.h"
#include "util/detour.h"
#include "util/logging.h"
#include "util/fileutils.h"
#include "util/unity_player.h"
#include "util/execexe.h"
#include "acioemu/handle.h"
#include "misc/wintouchemu.h"
#include "hooks/graphics/graphics.h"
#include "rawinput/rawinput.h"
namespace games::pc {
std::string PC_INJECT_ARGS = "";
bool PC_NO_IO = false;
static acioemu::ACIOHandle *acioHandle = nullptr;
static std::wstring portName = L"COM1";
static decltype(RegisterRawInputDevices) *RegisterRawInputDevices_orig = nullptr;
static decltype(EnumDisplaySettingsW) *EnumDisplaySettingsW_orig = nullptr;
static decltype(QueryDisplayConfig) *QueryDisplayConfig_orig = nullptr;
static BOOL WINAPI RegisterRawInputDevices_hook(PCRAWINPUTDEVICE pRawInputDevices, UINT uiNumDevices, UINT cbSize) {
// if the caller is spice itself, then pass through.
if (pRawInputDevices &&
(uiNumDevices > 0) &&
(pRawInputDevices[0].hwndTarget == RI_MGR->input_hwnd)) {
return RegisterRawInputDevices_orig(pRawInputDevices, uiNumDevices, cbSize);
}
// otherwise, it must be the game; prevent the game from registering for raw input
// and hijacking WM_INPUT messages.
SetLastError(0xDEADBEEF);
return FALSE;
}
static BOOL WINAPI EnumDisplaySettingsW_hook(LPCWSTR lpszDeviceName, DWORD iModeNum, DEVMODEW* lpDevMode) {
auto result = EnumDisplaySettingsW_orig(lpszDeviceName, iModeNum, lpDevMode);
// https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Screen-currentResolution.html
// "If the player is running in windowed mode, this returns the current dimensions of the game window in pixels and the display refresh rate."
// Override it to 1920x1080 to avoid broken UI under Windowed mode
if (result && iModeNum == ENUM_CURRENT_SETTINGS) {
// Actually make it returns size of the game window, Can I?
lpDevMode->dmPelsWidth = 1920;
lpDevMode->dmPelsHeight = 1080;
}
return result;
}
LONG WINAPI QueryDisplayConfig_hook(UINT32, UINT32*, DISPLAYCONFIG_PATH_INFO*, UINT32*, DISPLAYCONFIG_MODE_INFO*, DISPLAYCONFIG_TOPOLOGY_ID*) {
// make unity fallback to EnumDisplaySettingsW as I don't
// want to deal with this api which is way more complex
return ERROR_NOT_SUPPORTED;
}
void PCGame::attach() {
Game::attach();
// add card reader
acioHandle = new acioemu::ACIOHandle(portName.c_str(), 1);
devicehook_init_trampoline();
devicehook_add(acioHandle);
execexe::init();
execexe::init_deferred([] () {
execexe::init_port_hook(portName, acioHandle);
execexe::load_library("libaio.dll");
execexe::load_library("libaio-iob.dll");
execexe::load_library("libaio-iob_video.dll");
execexe::load_library("libaio-iob2_video.dll");
execexe::load_library("win10actlog.dll");
if (!PC_NO_IO) {
bi2x_hook_init();
}
});
const auto user32Dll = "user32.dll";
detour::trampoline_try(user32Dll, "RegisterRawInputDevices",
RegisterRawInputDevices_hook, &RegisterRawInputDevices_orig);
detour::trampoline_try(user32Dll, "QueryDisplayConfig",
QueryDisplayConfig_hook, &QueryDisplayConfig_orig);
detour::trampoline_try(user32Dll, "EnumDisplaySettingsW",
EnumDisplaySettingsW_hook, &EnumDisplaySettingsW_orig);
if (GRAPHICS_SHOW_CURSOR) {
unity_utils::force_show_cursor(true);
}
unity_utils::set_args(
std::format("{} {}{}",
GetCommandLineA(),
PC_INJECT_ARGS,
unity_utils::get_unity_player_args()));
}
void PCGame::detach() {
Game::detach();
devicehook_dispose();
}
}
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include "games/game.h"
namespace games::pc {
extern std::string PC_INJECT_ARGS;
extern bool PC_NO_IO;
extern bool PC_KNOB_MODE;
class PCGame : public games::Game {
public:
PCGame() : Game("Polaris Chord") {}
virtual void attach() override;
virtual void detach() override;
};
}
+17 -1
View File
@@ -14,6 +14,7 @@ namespace games::pcm {
static double BILL_DELAY = 2.0;
static int bill_kind = 0;
static int accept_bill_kind = 0;
static int __cdecl BillVali_GetEscrowBillKind() {
return bill_kind;
@@ -74,6 +75,12 @@ namespace games::pcm {
BILL_IN_TIME = get_performance_seconds();
} else if ((BILL_IN_TIME != 0) && ((BILL_IN_TIME + BILL_DELAY) <= get_performance_seconds())) {
BILL_IN_TIME = 0;
if (accept_bill_kind != 0 && accept_bill_kind != bill_kind) {
bill_kind = 0;
return 0;
}
switch (bill_kind) {
case 1:
return 1;
@@ -98,7 +105,8 @@ namespace games::pcm {
return true;
}
static bool __cdecl BillVali_SetAcceptBill(int) {
static bool __cdecl BillVali_SetAcceptBill(int a1) {
accept_bill_kind = a1;
return true;
}
@@ -148,5 +156,13 @@ namespace games::pcm {
system, "?GetEscrowBillKind@GsBillVali@@SAHXZ"));
detour::inline_hook(BillVali_SetAcceptBill, libutils::try_proc(
system, "?SetAcceptBill@GsBillVali@@SA_NH@Z"));
// NOTE: for 2024091200 or newer
detour::inline_hook(BillVali_ReceiveBill, libutils::try_proc(
system, "?ReceiveBill@GsBillVali@@SA_NXZ"));
detour::inline_hook(BillVali_GetEscrowBillKind, libutils::try_proc(
system, "?GetEscrowBillKind@GsBillVali@@SA?AW4E_BILLKIND@@XZ"));
detour::inline_hook(BillVali_SetAcceptBill, libutils::try_proc(
system, "?SetAcceptBill@GsBillVali@@SA_NW4E_BILLKIND@@@Z"));
}
}
+6 -116
View File
@@ -14,134 +14,24 @@
#include <mfidl.h>
#include "avs/game.h"
#include "hooks/cfgmgr32hook.h"
#include "util/detour.h"
#include "util/memutils.h"
#include "util/utils.h"
static VTBL_TYPE(IMFActivate, GetAllocatedString) GetAllocatedString_orig = nullptr;
static decltype(MFEnumDeviceSources) *MFEnumDeviceSources_orig = nullptr;
namespace games::sdvx {
static std::wstring CAMERA0_ID;
static HRESULT WINAPI GetAllocatedString_hook(IMFActivate* This, REFGUID guidKey, LPWSTR *ppwszValue,
UINT32 *pcchLength) {
// call the original
HRESULT result = GetAllocatedString_orig(This, guidKey, ppwszValue, pcchLength);
// try first camera
wchar_t *pwc = nullptr;
if (CAMERA0_ID.length() == 23)
pwc = wcsstr(*ppwszValue, CAMERA0_ID.c_str());
// check if camera could be identified
if (pwc) {
// fake the USB IDs
pwc[4] = L'2';
pwc[5] = L'8';
pwc[6] = L'8';
pwc[7] = L'c';
pwc[13] = L'0';
pwc[14] = L'0';
pwc[15] = L'0';
pwc[16] = L'2';
pwc[21] = L'0';
pwc[22] = L'0';
}
// return original result
return result;
}
static void hook_camera(IMFActivate* camera, size_t no, std::wstring camera_id, std::string camera_instance) {
// don't hook if camera 0 is already hooked
if (CAMERA0_ID.length() > 0)
return;
// save the camera ID
CAMERA0_ID = camera_id;
// cfgmgr hook
CFGMGR32_HOOK_SETTING camera_setting;
camera_setting.device_instance = 0xDEADBEEF;
camera_setting.parent_instance = ~camera_setting.device_instance;
camera_setting.device_id = "USB\\VEN_1022&DEV_7908";
camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????";
if (camera_instance.length() == 17) {
for (int i = 0; i < 17; i++) {
camera_setting.device_node_id[28 + i] = camera_instance[i];
}
}
cfgmgr32hook_add(camera_setting);
// save original method for later use
if (GetAllocatedString_orig == nullptr) {
GetAllocatedString_orig = camera->lpVtbl->GetAllocatedString;
}
// hook allocated string method for camera identification
memutils::VProtectGuard camera_guard(camera->lpVtbl);
camera->lpVtbl->GetAllocatedString = GetAllocatedString_hook;
}
static HRESULT WINAPI MFEnumDeviceSources_hook(IMFAttributes *pAttributes, IMFActivate ***pppSourceActivate,
UINT32 *pcSourceActivate) {
// call original function
HRESULT result_orig = MFEnumDeviceSources_orig(pAttributes, pppSourceActivate, pcSourceActivate);
// check for capture devices
if (FAILED(result_orig) || !*pcSourceActivate) {
return result_orig;
}
// iterate cameras
size_t cam_hook_num = 0;
for (size_t cam_num = 0; cam_num < *pcSourceActivate && cam_hook_num < 1; cam_num++) {
// flip
size_t cam_num_flipped = cam_num;
// get camera link
IMFActivate *camera = (*pppSourceActivate)[cam_num_flipped];
LPWSTR camera_link_lpwstr;
UINT32 camera_link_length;
if (SUCCEEDED(camera->lpVtbl->GetAllocatedString(
camera,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
&camera_link_lpwstr,
&camera_link_length))) {
// cut name to make ID
std::wstring camera_link_ws = std::wstring(camera_link_lpwstr);
std::wstring camera_id = camera_link_ws.substr(8, 23);
// get camera instance
std::string camera_link = ws2s(camera_link_ws);
std::string camera_instance = camera_link.substr(32, 17);
// hook the camera
hook_camera(camera, cam_hook_num, camera_id, camera_instance);
// increase camera hook number
cam_hook_num++;
}
}
// return result
return result_orig;
*pppSourceActivate = nullptr;
*pcSourceActivate = 0;
log_misc("sdvx", "MFEnumDeviceSources_hook called, returning 0 cameras");
return S_OK;
}
void camera_init() {
// camera media framework hook
MFEnumDeviceSources_orig = detour::iat_try(
"MFEnumDeviceSources", MFEnumDeviceSources_hook, avs::game::DLL_INSTANCE);
log_info("sdvx", "installing camera hooks...");
detour::iat_try("MFEnumDeviceSources", MFEnumDeviceSources_hook, avs::game::DLL_INSTANCE);
}
}
+61 -11
View File
@@ -4,6 +4,7 @@
#include "avs/game.h"
#include "games/shared/lcdhandle.h"
#include "games/io.h"
#include "hooks/audio/audio.h"
#include "hooks/graphics/graphics.h"
#include "hooks/devicehook.h"
@@ -12,7 +13,9 @@
#include "hooks/powrprof.h"
#include "hooks/sleephook.h"
#include "hooks/winuser.h"
#include "launcher/options.h"
#include "touch/touch.h"
#include "util/deferlog.h"
#include "util/detour.h"
#include "util/logging.h"
#include "util/sigscan.h"
@@ -24,6 +27,7 @@
#include "io.h"
#include "acioemu/handle.h"
#include "cfg/configurator.h"
#include "launcher/signal.h"
static decltype(RegCloseKey) *RegCloseKey_orig = nullptr;
static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr;
@@ -39,7 +43,6 @@ namespace games::sdvx {
const char *ORIGINAL_ASIO_DEVICE_NAME = "XONAR SOUND CARD(64)";
// settings
bool DISABLECAMS = false;
bool NATIVETOUCH = false;
uint8_t DIGITAL_KNOB_SENS = 16;
SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM;
@@ -105,8 +108,8 @@ namespace games::sdvx {
if (ENABLE_COM_PORT_SCAN_HOOK &&
lpSubKey != nullptr && phkResult != nullptr &&
_stricmp(lpSubKey, "HARDWARE\\DEVICEMAP\\SERIALCOMM") == 0) {
log_info("sdvx::io", "failing HKLM\\HARDWARE\\DEVICEMAP\\SERIALCOMM to force COM1 ICCA");
return 2; //ERROR_FILE_NOT_FOUND
log_info("sdvx::io", "hook access to HKLM\\HARDWARE\\DEVICEMAP\\SERIALCOMM to force COM1 ICCA");
return 2; // ERROR_FILE_NOT_FOUND
}
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult);
@@ -210,6 +213,19 @@ namespace games::sdvx {
out = "";
return true;
}
if (data.find("SuperstepSound: Audio device is not available") != std::string::npos) {
deferredlogs::defer_error_messages(deferredlogs::SUPERSTEP_SOUND_ERROR_MESSAGE);
return false;
}
if (data.find("W:ea3-pos: ea3_report_posev: no such node:") != std::string::npos) {
deferredlogs::defer_error_messages({
"bad prop\\ea3-config.xml detected by game",
std::string(" ") + data,
" this will most likely cause Server Busy errors in certain conditions",
" follow the FAQ to fix your ea3-config.xml and try again"
});
return false;
}
return false;
}
@@ -263,7 +279,7 @@ namespace games::sdvx {
AUTO_INSERT_CARD_COOLDOWN = 15.f;
// check bad model name
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("UFC")) {
log_fatal(
log_warning(
"sdvx",
"BAD MODEL NAME ERROR\n\n\n"
"!!! model name set to UFC, this is WRONG and will break your game !!!\n"
@@ -273,7 +289,41 @@ namespace games::sdvx {
"!!! !!!\n"
"!!! model name set to UFC, this is WRONG and will break your game !!!\n\n\n"
);
}
log_fatal(
"sdvx",
"BAD MODEL NAME ERROR - model name set to UFC, must be KFC instead");
}
#ifdef SPICE64 // SDVX5+ specific code
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
auto options = games::get_options(eamuse_get_game());
// check -monitor + UFC mode
if (!GRAPHICS_WINDOWED &&
options->at(launcher::Options::DisplayAdapter).is_active() &&
isValkyrieCabinetMode) {
log_warning(
"sdvx",
"\n\n!!! using -monitor option with VM mode is NOT recommended due to !!!\n"
"!!! known compatibility issues with the game !!!\n"
"!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n"
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
);
deferredlogs::defer_error_messages({
"-monitor option is NOT recommended when running with Valkyrie mode",
" due to known compatibility issues with the game:",
" * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay",
" recommended fix is to NOT use -monitor and instead set the primary",
" monitor in Windows settings before launching the game"
});
}
#endif
}
void SDVXGame::attach() {
@@ -369,17 +419,17 @@ namespace games::sdvx {
winuser_hook_init(avs::game::DLL_INSTANCE);
// hook camera
if (!DISABLECAMS) {
camera_init();
}
camera_init();
// RGB CAMERA error ignore
if (!replace_pattern(
// RGB CAMERA error ignore for SDVX5
// SDVX5: boot sequence triggers camera error if camera is not detected
// SDVX6: boots fine, but game title screen in attract loop will have camera error (cosmetic only)
if (replace_pattern(
avs::game::DLL_INSTANCE,
"418D480484C074218D51FD",
"????????????9090??????",
0, 0)) {
log_info("sdvx", "did not find matching signature for camera error patch");
log_info("sdvx", "applied camera error patch (sdvx5)");
}
// remove log spam
-1
View File
@@ -17,7 +17,6 @@ namespace games::sdvx {
};
// settings
extern bool DISABLECAMS;
extern bool NATIVETOUCH;
extern uint8_t DIGITAL_KNOB_SENS;
extern std::optional<std::string> ASIO_DRIVER;
+37 -17
View File
@@ -294,7 +294,7 @@ namespace games::shared {
// error
if (!clear) {
log_fatal("sdvx", "could not find path");
log_fatal("printer", "could not find path");
}
return "DUMMY";
@@ -326,7 +326,7 @@ namespace games::shared {
memcpy(image_data, pBandImage->baseAddr, (size_t) (image_width * image_height * 3));
// convert BGR to RGB
log_info("printer", "converting BGR to RGB...");
log_misc("printer", "converting BGR to RGB...");
for (int pixel = 0; pixel < image_width * image_height; pixel++) {
int index = pixel * 3;
uint8_t tmp = image_data[index];
@@ -334,25 +334,45 @@ namespace games::shared {
image_data[index + 2] = tmp;
}
// flip horizontally
for (int x = 0; x < image_width / 2; x++) {
for (int y = 0; y < image_height; y++) {
int index1 = (y * image_width + x) * 3;
int index2 = (y * image_width + image_width - x - 1) * 3;
uint8_t r = image_data[index1 + 0];
uint8_t g = image_data[index1 + 1];
uint8_t b = image_data[index1 + 2];
image_data[index1 + 0] = image_data[index2 + 0];
image_data[index1 + 1] = image_data[index2 + 1];
image_data[index1 + 2] = image_data[index2 + 2];
image_data[index2 + 0] = r;
image_data[index2 + 1] = g;
image_data[index2 + 2] = b;
if (avs::game::is_model({"KLP", "KFC"})) {
// rotate image clockwise
log_misc("printer", "rotate clockwise...");
auto rotated = new uint8_t[image_width * image_height * 3];
for (int x = 0; x < image_width; x++) {
for (int y = 0; y < image_height; y++) {
int index1 = (y * image_width + x) * 3;
int index2 = (x * image_height + y) * 3;
rotated[index2 + 0] = image_data[index1 + 0];
rotated[index2 + 1] = image_data[index1 + 1];
rotated[index2 + 2] = image_data[index1 + 2];
}
}
delete[] image_data;
image_data = rotated;
std::swap(image_width, image_height);
} else {
// flip horizontally
log_misc("printer", "flip horizontally...");
for (int x = 0; x < image_width / 2; x++) {
for (int y = 0; y < image_height; y++) {
int index1 = (y * image_width + x) * 3;
int index2 = (y * image_width + image_width - x - 1) * 3;
uint8_t r = image_data[index1 + 0];
uint8_t g = image_data[index1 + 1];
uint8_t b = image_data[index1 + 2];
image_data[index1 + 0] = image_data[index2 + 0];
image_data[index1 + 1] = image_data[index2 + 1];
image_data[index1 + 2] = image_data[index2 + 2];
image_data[index2 + 0] = r;
image_data[index2 + 1] = g;
image_data[index2 + 2] = b;
}
}
}
// iterate folders
log_info("printer", "writing files...");
log_misc("printer", "writing files...");
for (const auto &path : PRINTER_PATH) {
for (const auto &format : PRINTER_FORMAT) {
@@ -15,16 +15,19 @@
#include "cfg/screen_resize.h"
#include "games/iidx/iidx.h"
#include "games/sdvx/sdvx.h"
#include "games/mfc/mfc.h"
#include "games/io.h"
#include "hooks/graphics/graphics.h"
#include "launcher/launcher.h"
#include "launcher/options.h"
#include "launcher/signal.h"
#include "launcher/shutdown.h"
#include "misc/clipboard.h"
#include "misc/eamuse.h"
#include "misc/wintouchemu.h"
#include "overlay/overlay.h"
#include "util/detour.h"
#include "util/deferlog.h"
#include "util/flags_helper.h"
#include "util/libutils.h"
#include "util/logging.h"
@@ -203,15 +206,50 @@ static std::string presentation_interval2s(UINT presentation_interval) {
static void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params);
static void log_create_device_failure(HRESULT hresult) {
// only print once since some games will try CreateDevice multiple times on failure
static std::once_flag printed;
std::call_once(printed, [hresult]() {
// special case for popn
if (avs::game::is_model("M39")) {
deferredlogs::defer_error_messages({
fmt::format("D3D9 CreateDevice/CreateDeviceEx failed with {:#x}!", (UINT)hresult),
" possible popn music HD mode resolution issue",
" popn HD mode launches at 1360x768 (and NOT 1366x768) which is often",
" unsupported by many monitors; here are some possible workarounds:",
" * enable GPU resolution scaling in your GPU settings",
" * use Force Full Screen Resolution option, combined with image scaling (F11)",
" * run in windowed mode",
" * run in SD mode"
});
} else {
deferredlogs::defer_error_messages({
fmt::format("D3D9 CreateDevice/CreateDeviceEx failed with {:#x}!", (UINT)hresult),
" this is a common graphics / monitor issue",
" * double check any graphics options you configured in spicecfg",
" * double check that your monitor supports the resolution + refresh rate",
" combination that the game requires",
" * enable GPU-side resolution scaling in your GPU options as needed",
" * if you have three or more monitors, try unplugging them down to one or two,",
" or enable -graphics-force-single-adapter option",
" * failing all that, see if enabling windowed mode helps"
});
}
});
}
static bool is_dx9_on_12_enabled() {
bool result = false;
switch (GRAPHICS_9_ON_12_STATE) {
case DX9ON12_FORCE_OFF:
log_info("graphics::d3d9", "DirectX 9on12: forced OFF by user (-dx9on12)");
return false;
result = false;
break;
case DX9ON12_FORCE_ON:
log_info("graphics::d3d9", "DirectX 9on12: forced ON by user (-9on12 or -dx9on12)");
return true;
result = true;
break;
case DX9ON12_AUTO:
default:
@@ -219,14 +257,33 @@ static bool is_dx9_on_12_enabled() {
log_info(
"graphics::d3d9",
"DirectX 9on12: enabled automatically for current game (tip: use -dx9on12 to force on or off)");
return true;
result = true;
} else {
log_info(
"graphics::d3d9",
"DirectX 9on12: disabled by default, using DX9 (tip: use -dx9on12 to force on or off)");
return false;
result = false;
}
break;
}
if (GRAPHICS_9_ON_12_STATE == DX9ON12_FORCE_ON) {
if (avs::game::is_model("LDJ") && avs::game::is_ext(2023091500, MAXINT)) {
deferredlogs::defer_error_messages({
"dx9on12 was force enabled by user (-dx9on12)",
" IIDX31+ is known to be incompatible with DX 9on12, leading to blank screen or crashes",
" try again with -dx9on12 option set to default value"
});
} else {
deferredlogs::defer_error_messages({
"dx9on12 was force enabled by user (-dx9on12)",
" not very game is compatible with this, and can lead to crashes",
" try without force enabling this if you are seeing issues"
});
}
}
return result;
}
static IDirect3D9 *WINAPI Direct3DCreate9_hook(UINT SDKVersion) {
@@ -775,6 +832,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
// log error
log_info("graphics::d3d9", "IDirect3D9::CreateDevice failed, hr={}", FMT_HRESULT(ret));
log_create_device_failure(ret);
} else if (!D3D9_DEVICE_HOOK_DISABLE) {
graphics_hook_window(hFocusWindow, pPresentationParameters);
@@ -994,6 +1052,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
// log error
log_warning("graphics::d3d9", "CreateDeviceEx failed, hr={}", FMT_HRESULT(result));
log_create_device_failure(result);
} else if (!D3D9_DEVICE_HOOK_DISABLE) {
graphics_hook_window(hFocusWindow, pPresentationParameters);
@@ -1301,6 +1361,11 @@ void graphics_d3d9_on_present(
graphics_d3d9_ldj_on_present(wrapped_device);
}
const bool is_mfc = avs::game::is_model("KK9") && games::mfc::HG_MODE;
if (is_mfc) {
wintouchemu::update();
}
// check screenshot key
static bool trigger_last = false;
auto buttons = games::get_buttons_overlay(eamuse_get_game());
@@ -733,6 +733,16 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
const int rectTop = param.BackBufferHeight;
const int w = param.BackBufferWidth;
const int h = param.BackBufferHeight;
if (cfg::SCREENRESIZE->enable_screen_resize) {
RECT rect {
0,
0,
(LONG)topSurface_width,
(LONG)topSurface_height,
};
pReal->ColorFill(topSurface, &rect, D3DCOLOR_XRGB(0, 0, 0));
}
D3DLOCKED_RECT rect;
HRESULT hr;
@@ -750,6 +760,7 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
(LONG)(rectLeft + w),
(LONG)(rectTop + h),
};
if (GRAPHICS_FS_ORIENTATION_SWAP) {
originRect.left = (topSurface_width / 2) - (GRAPHICS_FS_ORIGINAL_WIDTH / 2);
originRect.top = (topSurface_height / 2) - (GRAPHICS_FS_ORIGINAL_HEIGHT / 2);
@@ -757,6 +768,21 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
originRect.bottom = originRect.top + GRAPHICS_FS_ORIGINAL_HEIGHT;
}
bool duplicate = false;
RECT originRect2 = originRect;
if (cfg::SCREENRESIZE->enable_screen_resize) {
auto& scene = cfg::SCREENRESIZE->scene_settings[cfg::SCREENRESIZE->screen_resize_current_scene];
if (scene.duplicate == cfg::ScreenDuplicateMode::CopyLeft) {
duplicate = true;
originRect2.right = originRect.left;
originRect2.left = (LONG)(originRect2.left - (originRect.right - originRect.left));
} else if (scene.duplicate == cfg::ScreenDuplicateMode::CopyRight) {
duplicate = true;
originRect2.left = originRect.right;
originRect2.right = (LONG)(originRect2.left + (originRect.right - originRect.left));
}
}
// log_misc(
// "graphics::d3d9", "originRect: {} {} {} {}",
// originRect.left, originRect.top, originRect.right, originRect.bottom);
@@ -768,9 +794,21 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
if (hr != D3D_OK) {
log_warning(
"graphics::d3d9",
"SurfaceHook - StretchRect backbuffer failed, rect: {} {} {} {}",
"SurfaceHook - StretchRect backbuffer failed for main image, rect: {} {} {} {}",
originRect.left, originRect.top, originRect.right, originRect.bottom);
}
if (duplicate) {
hr = pReal->StretchRect(
backbuffer, nullptr,
topSurface, &originRect2,
D3DTEXF_LINEAR);
if (hr != D3D_OK) {
log_warning(
"graphics::d3d9",
"SurfaceHook - StretchRect backbuffer failed for the duplicate, rect: {} {} {} {}",
originRect2.left, originRect2.top, originRect2.right, originRect2.bottom);
}
}
topSurface->UnlockRect();
// phase 2 - viewport calculation - do the actual zoom / offset math
+27 -3
View File
@@ -374,6 +374,27 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window) {
dwStyle &= ~(WS_MAXIMIZEBOX);
}
if ((is_tdj_sub_window || is_sdvx_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
dwStyle &= ~(WS_OVERLAPPEDWINDOW);
}
if (is_sdvx_sub_window) {
graphics_load_windowed_subscreen_parameters();
if (GRAPHICS_WSUB_SIZE.has_value()) {
nWidth = GRAPHICS_WSUB_WIDTH;
nHeight = GRAPHICS_WSUB_HEIGHT;
} else {
GRAPHICS_WSUB_WIDTH = nWidth;
GRAPHICS_WSUB_HEIGHT = nHeight;
}
if (GRAPHICS_WSUB_POS.has_value()) {
x = GRAPHICS_WSUB_X;
y = GRAPHICS_WSUB_Y;
} else {
GRAPHICS_WSUB_X = x;
GRAPHICS_WSUB_Y = y;
}
}
// call original
HWND result = CreateWindowExA_orig(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
@@ -529,11 +550,11 @@ static BOOL WINAPI MoveWindow_hook(HWND hWnd, int X, int Y, int nWidth, int nHei
dwStyle = GetWindowLongA(hWnd, GWL_STYLE);
SetRect(&rect, 0, 0, GRAPHICS_IIDX_WSUB_WIDTH, GRAPHICS_IIDX_WSUB_HEIGHT);
SetRect(&rect, 0, 0, GRAPHICS_WSUB_WIDTH, GRAPHICS_WSUB_HEIGHT);
AdjustWindowRect(&rect, dwStyle, 0);
X = GRAPHICS_IIDX_WSUB_X;
Y = GRAPHICS_IIDX_WSUB_Y;
X = GRAPHICS_WSUB_X;
Y = GRAPHICS_WSUB_Y;
nWidth = rect.right - rect.left;
nHeight = rect.bottom - rect.top;
@@ -865,6 +886,9 @@ void graphics_hook_subscreen_window(HWND hWnd) {
WSUB_WNDPROC_ORIG = reinterpret_cast<WNDPROC>(GetWindowLongPtrA(hWnd, GWLP_WNDPROC));
SetWindowLongPtrA(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WsubWindowProc));
}
if (GRAPHICS_WSUB_ALWAYS_ON_TOP) {
graphics_update_z_order(hWnd, true);
}
}
void graphics_screens_register(int screen) {
+9 -7
View File
@@ -50,12 +50,14 @@ extern bool GRAPHICS_WINDOW_ALWAYS_ON_TOP;
extern bool GRAPHICS_WINDOW_BACKBUFFER_SCALE;
extern bool GRAPHICS_IIDX_WSUB;
extern std::optional<std::string> GRAPHICS_IIDX_WSUB_SIZE;
extern std::optional<std::string> GRAPHICS_IIDX_WSUB_POS;
extern int GRAPHICS_IIDX_WSUB_WIDTH;
extern int GRAPHICS_IIDX_WSUB_HEIGHT;
extern int GRAPHICS_IIDX_WSUB_X;
extern int GRAPHICS_IIDX_WSUB_Y;
extern std::optional<std::string> GRAPHICS_WSUB_SIZE;
extern std::optional<std::string> GRAPHICS_WSUB_POS;
extern int GRAPHICS_WSUB_WIDTH;
extern int GRAPHICS_WSUB_HEIGHT;
extern int GRAPHICS_WSUB_X;
extern int GRAPHICS_WSUB_Y;
extern bool GRAPHICS_WSUB_BORDERLESS;
extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP;
extern HWND TDJ_SUBSCREEN_WINDOW;
extern HWND SDVX_SUBSCREEN_WINDOW;
@@ -94,7 +96,7 @@ std::string graphics_screenshot_genpath();
void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void graphics_capture_initial_window(HWND hWnd);
void graphics_update_window_style(HWND hWnd);
void graphics_update_z_order(HWND hWnd);
void graphics_update_z_order(HWND hWnd, bool always_on_top);
void graphics_move_resize_window(HWND hWnd);
bool graphics_window_change_crashes_game();
void graphics_load_windowed_subscreen_parameters();
@@ -26,12 +26,14 @@ bool GRAPHICS_WINDOW_BACKBUFFER_SCALE = false;
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
bool GRAPHICS_IIDX_WSUB = false;
std::optional<std::string> GRAPHICS_IIDX_WSUB_SIZE;
std::optional<std::string> GRAPHICS_IIDX_WSUB_POS;
int GRAPHICS_IIDX_WSUB_WIDTH = 1280;
int GRAPHICS_IIDX_WSUB_HEIGHT = 720;
int GRAPHICS_IIDX_WSUB_X = 0;
int GRAPHICS_IIDX_WSUB_Y = 0;
std::optional<std::string> GRAPHICS_WSUB_SIZE;
std::optional<std::string> GRAPHICS_WSUB_POS;
int GRAPHICS_WSUB_WIDTH = 1280;
int GRAPHICS_WSUB_HEIGHT = 720;
int GRAPHICS_WSUB_X = 0;
int GRAPHICS_WSUB_Y = 0;
bool GRAPHICS_WSUB_BORDERLESS = false;
bool GRAPHICS_WSUB_ALWAYS_ON_TOP = false;
// these flags are carefully constructed to ensure maximum compatibility
// (e.g., DDR likes to hang when SetWindowPos is called with certain params)
@@ -108,7 +110,7 @@ void graphics_capture_initial_window(HWND hWnd) {
}
if (cfg::SCREENRESIZE->window_always_on_top) {
log_info("graphics-windowed", "change window z-order - always on top");
graphics_update_z_order(hWnd);
graphics_update_z_order(hWnd, true);
}
// ensure spicetouch coordinates are initialized
@@ -169,29 +171,29 @@ void graphics_load_windowed_subscreen_parameters() {
log_debug("graphics-windowed", "graphics_load_windowed_subscreen_parameters called");
if (GRAPHICS_IIDX_WSUB_SIZE.has_value()) {
if (GRAPHICS_WSUB_SIZE.has_value()) {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_IIDX_WSUB_SIZE");
"graphics_load_windowed_parameters - load GRAPHICS_WSUB_SIZE");
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(GRAPHICS_IIDX_WSUB_SIZE.value(), result)) {
GRAPHICS_IIDX_WSUB_WIDTH = result.first;
GRAPHICS_IIDX_WSUB_HEIGHT = result.second;
if (parse_width_height(GRAPHICS_WSUB_SIZE.value(), result)) {
GRAPHICS_WSUB_WIDTH = result.first;
GRAPHICS_WSUB_HEIGHT = result.second;
} else {
log_warning("graphics-windowed", "failed to parse -wsubsize");
}
}
if (GRAPHICS_IIDX_WSUB_POS.has_value()) {
if (GRAPHICS_WSUB_POS.has_value()) {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_IIDX_WSUB_POS");
"graphics_load_windowed_parameters - load GRAPHICS_WSUB_POS");
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(GRAPHICS_IIDX_WSUB_POS.value(), result)) {
GRAPHICS_IIDX_WSUB_X = result.first;
GRAPHICS_IIDX_WSUB_Y = result.second;
if (parse_width_height(GRAPHICS_WSUB_POS.value(), result)) {
GRAPHICS_WSUB_X = result.first;
GRAPHICS_WSUB_Y = result.second;
} else {
log_warning("graphics-windowed", "failed to parse -wsubpos");
}
@@ -398,7 +400,7 @@ void graphics_update_window_style(HWND hWnd) {
log_debug("graphics-windowed", "graphics_update_window_style returned");
}
void graphics_update_z_order(HWND hWnd) {
void graphics_update_z_order(HWND hWnd, bool always_on_top) {
if (!GRAPHICS_WINDOWED) {
return;
}
@@ -406,7 +408,7 @@ void graphics_update_z_order(HWND hWnd) {
log_debug("graphics-windowed", "graphics_update_z_order called");
HWND insert_after = nullptr;
if (cfg::SCREENRESIZE->window_always_on_top) {
if (always_on_top) {
insert_after = HWND_TOPMOST;
} else {
insert_after = HWND_NOTOPMOST;
+129 -20
View File
@@ -9,6 +9,7 @@
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/utils.h"
#include "nvenc_hook.h"
@@ -16,9 +17,13 @@
typedef NVENCSTATUS(NVENCAPI *NvEncodeAPICreateInstance_Type)(NV_ENCODE_API_FUNCTION_LIST*);
static NvEncodeAPICreateInstance_Type NvEncodeAPICreateInstance_orig = nullptr;
static PNVENCOPENENCODESESSIONEX nvEncOpenEncodeSessionEx_orig = nullptr;
static PNVENCINITIALIZEENCODER nvEncInitializeEncoder_orig = nullptr;
static PNVENCGETENCODEPRESETCONFIG nvEncGetEncodePresetConfig_orig = nullptr;
static PNVENCGETENCODEPRESETCONFIGEX nvEncGetEncodePresetConfigEx_orig = nullptr;
static BOOL initialized = false;
static BOOL new_preset_guids = false;
namespace nvenc_hook {
@@ -27,6 +32,37 @@ namespace nvenc_hook {
void parse_qcp_params();
void dump_init_params(NV_ENC_INITIALIZE_PARAMS* encode) {
log_misc("nvenc_hook", "NV_ENC_INITIALIZE_PARAMS:");
log_misc("nvenc_hook", " version: {:x}", encode->version);
log_misc("nvenc_hook", " encodeGUID: {}", guid2s(encode->encodeGUID));
log_misc("nvenc_hook", " presetGUID: {}", guid2s(encode->presetGUID));
log_misc("nvenc_hook", " encodeWidth: {}", encode->encodeWidth);
log_misc("nvenc_hook", " encodeHeight: {}", encode->encodeHeight);
log_misc("nvenc_hook", " darWidth: {}", encode->darWidth);
log_misc("nvenc_hook", " darHeight: {}", encode->darHeight);
log_misc("nvenc_hook", " frameRateNum: {}", encode->frameRateNum);
log_misc("nvenc_hook", " frameRateDen: {}", encode->frameRateDen);
log_misc("nvenc_hook", " enableEncodeAsync: {}", encode->enableEncodeAsync);
log_misc("nvenc_hook", " enablePTD: {}", encode->enablePTD);
log_misc("nvenc_hook", " maxEncodeWidth: {}", encode->maxEncodeWidth);
log_misc("nvenc_hook", " maxEncodeHeight: {}", encode->maxEncodeHeight);
log_misc("nvenc_hook", " tuningInfo: {}", static_cast<uint32_t>(encode->tuningInfo));
log_misc("nvenc_hook", " bufferFormat: {}", static_cast<uint32_t>(encode->bufferFormat));
log_misc("nvenc_hook", " encodeConfig.version: {:x}", encode->encodeConfig->version);
log_misc("nvenc_hook", " encodeConfig.profileGUID: {}", guid2s(encode->encodeConfig->profileGUID));
log_misc("nvenc_hook", " encodeConfig.gopLength: {}", encode->encodeConfig->gopLength);
log_misc("nvenc_hook", " encodeConfig.frameIntervalP: {}", encode->encodeConfig->frameIntervalP);
log_misc("nvenc_hook", " encodeConfig.monoChromeEncoding: {}", encode->encodeConfig->monoChromeEncoding);
const auto rc = &encode->encodeConfig->rcParams;
const auto h264 = &encode->encodeConfig->encodeCodecConfig.h264Config;
log_misc("nvenc_hook", " encodeConfig.encodeCodecConfig.h264Config.sliceMode: {}", h264->sliceMode);
log_misc("nvenc_hook", " encodeConfig.encodeCodecConfig.h264Config.sliceModeData: {}", h264->sliceModeData);
log_misc("nvenc_hook", " encodeConfig.rcParams.version: {:x}", rc->version);
log_misc("nvenc_hook", " encodeConfig.rcParams.rateControlMode: {}", static_cast<uint32_t>(rc->rateControlMode));
}
NVENCSTATUS NVENCAPI nvEncOpenEncodeSessionEx_hook(
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS *openSessionExParams,
void **encoder
@@ -51,38 +87,105 @@ namespace nvenc_hook {
try {
// check input params
if (createEncodeParams == nullptr || createEncodeParams->encodeConfig == nullptr) {
log_warning("nvenc_hook", "nvEncInitializeEncoder called with invalid params");
goto done;
}
const auto rc = &createEncodeParams->encodeConfig->rcParams;
if (rc->rateControlMode != NV_ENC_PARAMS_RC_CONSTQP) {
log_misc("nvenc_hook", "nvEncInitializeEncoder hook hit with expected params");
dump_init_params(createEncodeParams);
if (new_preset_guids) {
log_misc("nvenc_hook", "swapping out encoder preset for newer NVENC SDK");
memcpy(&createEncodeParams->presetGUID, &NV_ENC_PRESET_P4_GUID, sizeof(GUID));
createEncodeParams->tuningInfo = NV_ENC_TUNING_INFO_HIGH_QUALITY;
}
// cqp p/b/i override
if (rc->rateControlMode == NV_ENC_PARAMS_RC_CONSTQP) {
// print out most relevant video quality settings
// note: NvEncoder.cpp sample uses {28, 31, 25} (and that's what some hex edits modify)
// but bm2dx later adds 8 to each value, before calling this routine
log_misc(
"nvenc_hook", "nvEncInitializeEncoder: original constQP p={}, b={}, i={}",
rc->constQP.qpInterP, rc->constQP.qpInterB, rc->constQP.qpIntra);
// video quality override
if (VIDEO_CQP_OVERRIDE.has_value()) {
rc->constQP = VIDEO_CQP_OVERRIDE.value();
log_misc(
"nvenc_hook", "nvEncInitializeEncoder: user overriden constQP p={}, b={}, i={}",
rc->constQP.qpInterP, rc->constQP.qpInterB, rc->constQP.qpIntra);
}
} else {
log_warning(
"nvenc_hook",
"nvEncInitializeEncoder: unexpected rateControlMode, expected: NV_ENC_PARAMS_RC_CONSTQP, actual: {}",
rc->rateControlMode);
goto done;
}
log_misc("nvenc_hook", "nvEncInitializeEncoder hook hit with expected params");
// print out most relevant video quality settings
// note: NvEncoder.cpp sample uses {28, 31, 25} (and that's what some hex edits modify)
// but bm2dx later adds 8 to each value, before calling this routine
log_misc(
"nvenc_hook", "nvEncInitializeEncoder: original constQP p={}, b={}, i={}",
rc->constQP.qpInterP, rc->constQP.qpInterB, rc->constQP.qpIntra);
// video quality override
if (VIDEO_CQP_OVERRIDE.has_value()) {
rc->constQP = VIDEO_CQP_OVERRIDE.value();
log_misc(
"nvenc_hook", "nvEncInitializeEncoder: user overriden constQP p={}, b={}, i={}",
rc->constQP.qpInterP, rc->constQP.qpInterB, rc->constQP.qpIntra);
}
} catch (const std::exception &ex) {}
done:
return nvEncInitializeEncoder_orig(encoder, createEncodeParams);
const auto status = nvEncInitializeEncoder_orig(encoder, createEncodeParams);
log_misc(
"nvenc_hook",
"nvEncInitializeEncoder returned 0x{:x}",
static_cast<uint32_t>(status));
return status;
}
NVENCSTATUS NVENCAPI nvEncGetEncodePresetConfig_hook (
void* encoder, GUID encodeGUID, GUID presetGUID, NV_ENC_PRESET_CONFIG* presetConfig) {
// IIDX32 calls this with
// presetGUID = {34DBA71D-A77B-4B8F-9C3E-B6D5DA24C012} (NV_ENC_PRESET_HQ_GUID) (for h264)
// this preset is deprecated according to NVIDIA
const auto status = nvEncGetEncodePresetConfig_orig(
encoder, encodeGUID, presetGUID, presetConfig);
log_misc(
"nvenc_hook",
"NvEncGetEncodePresetConfig called with encodeGUID = {}, presetGUID = {}. and returned 0x{:x}",
guid2s(encodeGUID),
guid2s(presetGUID),
static_cast<uint32_t>(status));
if (status == NV_ENC_SUCCESS) {
return status;
}
// in NVIDIA driver 591.44 released in December 2025,
// NvEncGetEncodePresetConfig started to fail with NV_ENC_ERR_UNSUPPORTED_PARAM and
// eventually cause a crash
//
// IIDX32 calls this with
// presetGUID = {34DBA71D-A77B-4B8F-9C3E-B6D5DA24C012} (NV_ENC_PRESET_HQ_GUID) (for h264)
// this preset is deprecated according to NVIDIA
// https://docs.nvidia.com/video-technologies/video-codec-sdk/13.0/deprecation-notices/index.html
//
// references:
// https://github.com/NVIDIA/video-sdk-samples/tree/aa3544dcea2fe63122e4feb83bf805ea40e58dbe/Samples/NvCodec/NvEncoder
// https://forums.developer.nvidia.com/t/drivers-591-44-broke-nvenc-getencodepresetconfig-no-longer-works/353613
// https://docs.nvidia.com/video-technologies/video-codec-sdk/11.1/nvenc-preset-migration-guide/index.html
new_preset_guids = true;
const auto status_ex =
nvEncGetEncodePresetConfigEx_orig(
encoder,
encodeGUID,
NV_ENC_PRESET_P4_GUID,
NV_ENC_TUNING_INFO_HIGH_QUALITY,
presetConfig);
log_misc(
"nvenc_hook",
"called NvEncGetEncodePresetConfigEx instead; returned 0x{:x}",
static_cast<uint32_t>(status_ex));
return status_ex;
}
NVENCSTATUS NVENCAPI NvEncodeAPICreateInstance_hook(NV_ENCODE_API_FUNCTION_LIST *pFunctionList) {
@@ -101,6 +204,11 @@ namespace nvenc_hook {
pFunctionList->nvEncInitializeEncoder,
nvEncInitializeEncoder_hook,
&nvEncInitializeEncoder_orig);
detour::trampoline_try(
pFunctionList->nvEncGetEncodePresetConfig,
nvEncGetEncodePresetConfig_hook,
&nvEncGetEncodePresetConfig_orig);
nvEncGetEncodePresetConfigEx_orig = pFunctionList->nvEncGetEncodePresetConfigEx;
log_misc("nvenc_hook", "NvEncodeAPICreateInstance_hook called and functions hooked");
initialized = true;
}
@@ -123,6 +231,7 @@ namespace nvenc_hook {
} else {
log_warning("nvenc_hook", "failed to hook NvEncodeAPICreateInstance");
}
parse_qcp_params();
}
+77
View File
@@ -8,6 +8,7 @@
#include <ntstatus.h>
#include "avs/game.h"
#include "games/iidx/iidx.h"
#include "util/detour.h"
#include "util/logging.h"
#include "util/utils.h"
@@ -18,9 +19,11 @@ constexpr UINT CODEPAGE_SHIFT_JIS = 932;
static decltype(GetACP) *GetACP_orig = nullptr;
static decltype(GetOEMCP) *GetOEMCP_orig = nullptr;
static decltype(MultiByteToWideChar) *MultiByteToWideChar_orig = nullptr;
static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
#ifdef SPICE64
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
#endif
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
@@ -119,6 +122,59 @@ static int WINAPI MultiByteToWideChar_hook(
cchWideChar);
}
static int WINAPI GetLocaleInfoEx_hook (
LPCWSTR lpLocaleName,
LCTYPE LCType,
LPWSTR lpLCData,
int cchData)
{
#if 0
if (lpLocaleName == LOCALE_NAME_INVARIANT) {
log_misc("hooks::lang", "GetLocaleInfoEx_hook hit (LOCALE_NAME_INVARIANT), {}, {}", LCType, cchData);
} else if (lpLocaleName == LOCALE_NAME_SYSTEM_DEFAULT) {
log_misc("hooks::lang", "GetLocaleInfoEx_hook hit (LOCALE_NAME_SYSTEM_DEFAULT), {}, {}", LCType, cchData);
} else if (lpLocaleName == LOCALE_NAME_USER_DEFAULT) {
log_misc("hooks::lang", "GetLocaleInfoEx_hook hit (LOCALE_NAME_USER_DEFAULT), {}, {}", LCType, cchData);
} else {
log_misc("hooks::lang", "GetLocaleInfoEx_hook hit ({}), {}, {}", ws2s(lpLocaleName), LCType, cchData);
}
#endif
if (lpLocaleName == LOCALE_NAME_USER_DEFAULT &&
LCType == LOCALE_SISO639LANGNAME &&
lpLCData != NULL &&
cchData >= 3) {
log_misc("hooks::lang",
"GetLocaleInfoEx_hook hit (LOCALE_NAME_USER_DEFAULT / LOCALE_SISO639LANGNAME), return `ja`");
wcscpy(lpLCData, L"ja");
return 3;
}
if (lpLocaleName == LOCALE_NAME_USER_DEFAULT &&
LCType == LOCALE_SISO3166CTRYNAME &&
lpLCData != NULL &&
cchData >= 3) {
log_misc("hooks::lang",
"GetLocaleInfoEx_hook hit (LOCALE_NAME_USER_DEFAULT / LOCALE_SISO3166CTRYNAME), return `JP`");
wcscpy(lpLCData, L"JP");
return 3;
}
return GetLocaleInfoEx_orig(lpLocaleName, LCType, lpLCData, cchData);
}
#ifdef SPICE64
static BOOL WINAPI IsDBCSLeadByte_hook (
BYTE TestChar
)
{
return IsDBCSLeadByteEx(CODEPAGE_SHIFT_JIS, TestChar);
}
#endif
void hooks::lang::early_init() {
log_info("hooks::lang", "early initialization");
@@ -138,6 +194,27 @@ void hooks::lang::early_init() {
}
#endif
if (avs::game::is_model("XIF")) {
log_info("hooks::lang", "hooking GetLocaleInfoEx");
detour::trampoline_try(
"kernel32.dll",
"GetLocaleInfoEx",
GetLocaleInfoEx_hook,
&GetLocaleInfoEx_orig);
}
#ifdef SPICE64
// for TDJ subscreen search keyboard
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
log_info("hooks::lang", "hooking IsDBCSLeadByte");
detour::trampoline_try(
"kernel32.dll",
"IsDBCSLeadByte",
IsDBCSLeadByte_hook,
&IsDBCSLeadByte_orig);
}
#endif
}
void hooks::lang::init() {
+5 -1
View File
@@ -141,13 +141,17 @@ static ULONG WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO pAdapterInfo, PULONG p
static int WINAPI bind_hook(SOCKET s, const struct sockaddr *name, int namelen) {
#ifdef __clang__
#pragma clang diagnostic push
#pragma ide diagnostic ignored "OCDFAInspection"
#endif
// cast to sockaddr_in
struct sockaddr_in *in_name = (struct sockaddr_in *) name;
#ifdef __clang__
#pragma clang diagnostic pop
#endif
// override bind to allow all hosts
in_name->sin_addr.s_addr = inet_addr("0.0.0.0");
@@ -179,7 +183,7 @@ void networkhook_init() {
strncpy(s_prefix, inet_ntoa(prefix), 16);
// log preferences
log_info("network", "Network preferences: {}", s_network, s_subnet, s_prefix);
// log_info("network", "Network preferences: {}, {}, {}", s_network, s_subnet, s_prefix);
// GetAdaptersInfo hook
auto orig_addr = detour::iat_try(
+351 -46
View File
@@ -3,9 +3,11 @@
#include <memory>
#include <vector>
#include <assert.h>
#include <shlwapi.h>
#include <cfg/configurator.h>
#include "api/modules/capture.h"
#include "acio/acio.h"
#include "acio/icca/icca.h"
#include "api/controller.h"
@@ -19,7 +21,9 @@
#include "cfg/screen_resize.h"
#include "easrv/easrv.h"
#include "external/cardio/cardio_runner.h"
#ifndef NO_SCARD
#include "external/scard/scard.h"
#endif
#include "games/game.h"
#include "games/io.h"
#include "games/bbc/bbc.h"
@@ -60,6 +64,7 @@
#include "games/ccj/trackball.h"
#include "games/qks/qks.h"
#include "games/mfg/mfg.h"
#include "games/pc/pc.h"
#include "games/museca/museca.h"
#include "hooks/avshook.h"
#include "hooks/audio/audio.h"
@@ -95,6 +100,7 @@
#include "touch/touch.h"
#include "util/cpuutils.h"
#include "util/crypt.h"
#include "util/deferlog.h"
#include "util/fileutils.h"
#include "util/libutils.h"
#include "util/logging.h"
@@ -149,6 +155,9 @@ static bool check_dll(const std::string &model) {
void update_msvcrt_args(int argc, char *argv[]);
void dump_button_bindings(std::vector<Button> *buttons);
void dump_analog_bindings();
int main_implementation(int argc, char *argv[]) {
// remember argv, argv
@@ -218,6 +227,7 @@ int main_implementation(int argc, char *argv[]) {
bool attach_ccj = false;
bool attach_qks = false;
bool attach_mfg = false;
bool attach_pc = false;
bool attach_museca = false;
bool show_cursor_if_no_touch = false;
@@ -243,6 +253,11 @@ int main_implementation(int argc, char *argv[]) {
// parse arguments
LAUNCHER_OPTIONS = launcher::parse_options(argc, argv);
// command line override (must be done before merging options with cfg)
if (LAUNCHER_OPTIONS->at(launcher::Options::OptionConflictResolution).value_bool()) {
launcher::USE_CMD_OVERRIDE = true;
}
// determine config file path - must be done before anything else
const auto &cfg_path = LAUNCHER_OPTIONS->at(launcher::Options::ConfigurationPath);
if (cfg_path.is_active()) {
@@ -251,7 +266,8 @@ int main_implementation(int argc, char *argv[]) {
// detect model used to load option overrides
auto options_version = launcher::detect_gameversion(
LAUNCHER_OPTIONS->at(launcher::Options::PathToEa3Config).value
LAUNCHER_OPTIONS->at(launcher::Options::PathToEa3Config).value,
LAUNCHER_OPTIONS->at(launcher::Options::PathToBootstrap).value
);
if (!options_version.model.empty() && options_version.model.size() < 4) {
if (options_version.dest.size() == 1) {
@@ -284,25 +300,14 @@ int main_implementation(int argc, char *argv[]) {
cfg::CONFIGURATOR_TYPE = cfg::ConfigType::Config;
cfg_run = true;
}
// -ea, but ignored if -url is set
if (options[launcher::Options::EAmusementEmulation].value_bool() &&
options[launcher::Options::ServiceURL].is_active() &&
!cfg::CONFIGURATOR_STANDALONE) {
log_fatal(
"launcher",
"BAD EAMUSE SETTINGS ERROR\n\n\n"
"-------------------------------------------------------------------\n"
"WARNING - WARNING - WARNING - WARNING - WARNING - WARNING - WARNING\n"
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
"A service URL is set **AND** E-Amusement emulation is enabled.\n"
"Either remove the service URL, or disable E-Amusement emulation.\n"
"Otherwise you may experience problems logging in.\n"
"-------------------------------------------------------------------\n\n\n"
);
}
if (options[launcher::Options::EAmusementEmulation].value_bool()) {
!options[launcher::Options::ServiceURL].is_active()) {
avs::ea3::URL_SLASH = 1;
easrv_port = 8080;
}
if (options[launcher::Options::SmartEAmusement].value_bool()) {
easrv_smart = true;
}
@@ -353,6 +358,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::VerboseAVSLogging].value_bool()) {
hooks::avs::config::LOG = true;
}
if (options[launcher::Options::AllowEA3Verbose].value_bool()) {
avs::ea3::EA3_DEBUG_VERBOSE = true;
}
if (options[launcher::Options::spice2x_AutoOrientation].is_active()) {
GRAPHICS_ADJUST_ORIENTATION =
(graphics_orientation)options[launcher::Options::spice2x_AutoOrientation].value_uint32();
@@ -423,9 +431,6 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::LoadSoundVoltexModule].value_bool()) {
attach_sdvx = true;
}
if (options[launcher::Options::SDVXDisableCameras].value_bool()) {
games::sdvx::DISABLECAMS = true;
}
if (options[launcher::Options::SDVXNativeTouch].value_bool()) {
games::sdvx::NATIVETOUCH = true;
}
@@ -458,14 +463,26 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::IIDXCameraOrderFlip].value_bool()) {
games::iidx::FLIP_CAMS = true;
}
// IIDX CONNECT_CAMERA logic here for cases where user is running without -iidx module
// for now, we assume that user may be running on a cab
// (games::iidx::DISABLE_CAMS starts out as false unless user overrides)
// we will check again in IIDX module with a different default
assert(!games::iidx::DISABLE_CAMS.has_value());
if (options[launcher::Options::IIDXDisableCameras].value_bool()) {
games::iidx::DISABLE_CAMS = true;
}
if (options[launcher::Options::IIDXCabCamAccess].is_active() &&
options[launcher::Options::IIDXCabCamAccess].value_text() == "off") {
games::iidx::DISABLE_CAMS = true;
}
if (options[launcher::Options::IIDXCamHook].value_bool()) {
games::iidx::TDJ_CAMERA = true;
// Disable legacy behaviour to avoid conflict
games::iidx::DISABLE_CAMS = true;
}
// CONNECT_CAMERA env var will be set once logging is enabled
if (options[launcher::Options::IIDXCamHookOverride].is_active()) {
games::iidx::TDJ_CAMERA_OVERRIDE = options[launcher::Options::IIDXCamHookOverride].value_text();
}
@@ -616,12 +633,18 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::LoadMFGModule].value_bool()) {
attach_mfg = true;
}
if (options[launcher::Options::LoadPCModule].value_bool()) {
attach_pc = true;
}
if (options[launcher::Options::LoadMusecaModule].value_bool()) {
attach_museca = true;
}
if (options[launcher::Options::DDR43Mode].value_bool()) {
games::ddr::SDMODE = true;
}
if (options[launcher::Options::DDRSkipCodecRegisteration].value_bool()) {
games::ddr::NO_CODEC_REGISTRATION = true;
}
if (options[launcher::Options::LoadSteelChronicleModule].value_bool()) {
attach_sc = true;
}
@@ -661,21 +684,42 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::SDVXPrinterEmulation].value_bool()) {
attach_cpusbxpkm_printer = true;
}
if (options[launcher::Options::SDVXPrinterOutputOverwrite].value_bool()) {
// printer overwrite
if (options[launcher::Options::SDVXPrinterOutputOverwrite].value_bool() ||
options[launcher::Options::LovePlusPrinterOutputOverwrite].value_bool()) {
games::shared::PRINTER_OVERWRITE_FILE = true;
}
// printer output path
for (auto &path : options[launcher::Options::SDVXPrinterOutputPath].values_text()) {
games::shared::PRINTER_PATH.push_back(path);
}
for (auto &path : options[launcher::Options::LovePlusPrinterOutputPath].values_text()) {
games::shared::PRINTER_PATH.push_back(path);
}
// printer file format
for (auto &path : options[launcher::Options::SDVXPrinterOutputFormat].values_text()) {
games::shared::PRINTER_FORMAT.push_back(path);
}
for (auto &path : options[launcher::Options::LovePlusPrinterOutputFormat].values_text()) {
games::shared::PRINTER_FORMAT.push_back(path);
}
// printer JPG quality
if (options[launcher::Options::SDVXPrinterJPGQuality].is_active()) {
games::shared::PRINTER_JPG_QUALITY = options[launcher::Options::SDVXPrinterJPGQuality].value_uint32();
} else if (options[launcher::Options::LovePlusPrinterJPGQuality].is_active()) {
games::shared::PRINTER_JPG_QUALITY = options[launcher::Options::LovePlusPrinterJPGQuality].value_uint32();
}
if (options[launcher::Options::SDVXPrinterOutputClear].value_bool()) {
// printer output clear
if (options[launcher::Options::SDVXPrinterOutputClear].value_bool() ||
options[launcher::Options::LovePlusPrinterOutputClear].value_bool()) {
games::shared::PRINTER_CLEAR = true;
}
if (options[launcher::Options::HTTP11].is_active()) {
avs::ea3::HTTP11 = options[launcher::Options::HTTP11].value_uint32();
}
@@ -776,6 +820,7 @@ int main_implementation(int argc, char *argv[]) {
for (auto &sextet : options[launcher::Options::SextetStreamPort].values_text()) {
sextet_devices.emplace_back(sextet);
}
#ifndef NO_SCARD
if (options[launcher::Options::HIDSmartCard].value_bool()) {
WINSCARD_CONFIG.cardinfo_callback = eamuse_scard_callback;
scard_threadstart();
@@ -795,6 +840,7 @@ int main_implementation(int argc, char *argv[]) {
WINSCARD_CONFIG.add_padding_to_felica = true;
}
}
#endif
if (options[launcher::Options::CardIOHIDReaderOrderFlip].value_bool()) {
CARDIO_RUNNER_FLIP = true;
}
@@ -813,6 +859,7 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::AdapterSubnet].is_active()) {
NETWORK_SUBNET = options[launcher::Options::AdapterSubnet].value_text();
}
if (options[launcher::Options::APITCPPort].is_active()) {
api_enable = true;
api_port = options[launcher::Options::APITCPPort].value_uint32();
@@ -835,6 +882,13 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::APIDebugMode].value_bool()) {
api_debug = true;
}
if (options[launcher::Options::APIScreenMirrorQuality].is_active()) {
api::modules::CAPTURE_QUALITY = options[launcher::Options::APIScreenMirrorQuality].value_uint32();
}
if (options[launcher::Options::APIScreenMirrorDivide].is_active()) {
api::modules::CAPTURE_DIVIDE = options[launcher::Options::APIScreenMirrorDivide].value_uint32();
}
if (options[launcher::Options::DisableDebugHooks].value_bool()) {
debughook::DEBUGHOOK_LOGGING = false;
}
@@ -890,6 +944,11 @@ int main_implementation(int argc, char *argv[]) {
}
if (options[launcher::Options::DisableAudioHooks].value_bool()) {
hooks::audio::ENABLED = false;
deferredlogs::defer_error_messages({
"audio hooks are forcibly disabled by user (-audiohookdisable)",
" having hooks disabled means some required audio fixes are not being applied",
" if you have audio-related crashes, you should try again after clearing this option"
});
}
if (options[launcher::Options::spice2x_DisableVolumeHook].value_bool()) {
hooks::audio::VOLUME_HOOK_ENABLED = false;
@@ -976,12 +1035,24 @@ int main_implementation(int argc, char *argv[]) {
GRAPHICS_WINDOW_ALWAYS_ON_TOP = options[launcher::Options::spice2x_WindowAlwaysOnTop].value_bool();
GRAPHICS_WINDOW_BACKBUFFER_SCALE = options[launcher::Options::WindowForceScaling].value_bool();
// IIDX Windowed Subscreen
// IIDX/SDVX Windowed Subscreen
if (options[launcher::Options::spice2x_IIDXWindowedSubscreenSize].is_active()) {
GRAPHICS_IIDX_WSUB_SIZE = options[launcher::Options::spice2x_IIDXWindowedSubscreenSize].value_text();
GRAPHICS_WSUB_SIZE = options[launcher::Options::spice2x_IIDXWindowedSubscreenSize].value_text();
} else if (options[launcher::Options::SDVXWindowedSubscreenSize].is_active()) {
GRAPHICS_WSUB_SIZE = options[launcher::Options::SDVXWindowedSubscreenSize].value_text();
}
if (options[launcher::Options::spice2x_IIDXWindowedSubscreenPosition].is_active()) {
GRAPHICS_IIDX_WSUB_POS = options[launcher::Options::spice2x_IIDXWindowedSubscreenPosition].value_text();
GRAPHICS_WSUB_POS = options[launcher::Options::spice2x_IIDXWindowedSubscreenPosition].value_text();
} else if (options[launcher::Options::SDVXWindowedSubscreenPosition].is_active()) {
GRAPHICS_WSUB_POS = options[launcher::Options::SDVXWindowedSubscreenPosition].value_text();
}
if (options[launcher::Options::IIDXWindowedSubscreenBorderless].value_bool() ||
options[launcher::Options::SDVXWindowedSubscreenBorderless].value_bool()) {
GRAPHICS_WSUB_BORDERLESS = true;
}
if (options[launcher::Options::IIDXWindowedSubscreenAlwaysOnTop].value_bool() ||
options[launcher::Options::SDVXWindowedSubscreenAlwaysOnTop].value_bool()) {
GRAPHICS_WSUB_ALWAYS_ON_TOP = true;
}
if (options[launcher::Options::spice2x_JubeatLegacyTouch].value_bool()) {
@@ -1050,12 +1121,32 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::MFGNoIO].is_active()) {
games::mfg::MFG_NO_IO = options[launcher::Options::MFGNoIO].value_bool();
}
if (options[launcher::Options::PCArgs].is_active()) {
games::pc::PC_INJECT_ARGS = options[launcher::Options::PCArgs].value_text();
}
if (options[launcher::Options::PCNoIO].is_active()) {
games::pc::PC_NO_IO = options[launcher::Options::PCNoIO].value_bool();
}
if (options[launcher::Options::PCKnobMode].value_bool()) {
games::pc::PC_KNOB_MODE = true;
}
if (options[launcher::Options::spice2x_EnableSMXStage].value_bool()) {
rawinput::ENABLE_SMX_STAGE = true;
}
if (options[launcher::Options::spice2x_EnableSMXDedicab].value_bool()) {
rawinput::ENABLE_SMX_DEDICAB = true;
}
if (options[launcher::Options::InputRequiresFocus].is_active()) {
const auto &text = options[launcher::Options::InputRequiresFocus].value_text();
if (text == "always") {
rawinput::NAIVE_REQUIRE_FOCUS = true;
rawinput::RAWINPUT_REQUIRE_FOCUS = true;
} else if (text == "never") {
rawinput::NAIVE_REQUIRE_FOCUS = false;
rawinput::RAWINPUT_REQUIRE_FOCUS = false;
}
}
if (options[launcher::Options::MidiAlgoVer].is_active()) {
if (options[launcher::Options::MidiAlgoVer].value_text() == "legacy") {
midi_algo = rawinput::MidiNoteAlgorithm::LEGACY;
@@ -1077,6 +1168,10 @@ int main_implementation(int argc, char *argv[]) {
rawinput::MIDI_NOTE_SUSTAIN = options[launcher::Options::MidiNoteSustain].value_uint32();
}
if (options[launcher::Options::LovePlusCamEnable].value_bool()) {
games::loveplus::CAMERA_ENABLE = true;
}
// API debugging
if (api_debug && !cfg::CONFIGURATOR_STANDALONE) {
API_CONTROLLER = std::make_unique<api::Controller>(api_port, api_pass, api_pretty);
@@ -1116,13 +1211,28 @@ int main_implementation(int argc, char *argv[]) {
}
// log
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x)");
#ifndef SPICE_LINUX
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x)");
#elif SPICE32_LARGE_ADDRESS_AWARE
log_info("launcher", "SpiceTools Bootstrap (x32 - Large Address Aware) (spice2x)");
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x)");
#endif
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x)");
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for Linux");
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for Linux");
#endif
#endif
log_info("launcher", "{}", VERSION_STRING);
// note: distribution of modified version of this software without providing source is GPLv3 license violation.
log_info("launcher", "spice2x is free & open source; if you paid money for it, you got scammed");
log_info("launcher", "visit https://spice2x.github.io to download the latest version for free");
// log command line arguments
std::ostringstream arguments;
for (auto &root_option : options) {
@@ -1161,6 +1271,52 @@ int main_implementation(int argc, char *argv[]) {
}
log_info("launcher", "arguments:\n{}", arguments.str());
// print out conflicts
size_t conflicts = 0;
for (const auto &option : options) {
if (option.conflicting && option.get_definition().type != OptionType::Bool) {
conflicts += 1;
const auto& value = option.get_definition().sensitive ? "*****" : option.value;
if (launcher::USE_CMD_OVERRIDE) {
log_warning(
"launcher",
"multiple values for -{}, command line args take precedence: {}",
option.get_definition().name,
value);
} else {
log_warning(
"launcher",
"multiple values for -{}, spicecfg values take precedence: {}",
option.get_definition().name,
value);
}
}
}
if (conflicts) {
if (launcher::USE_CMD_OVERRIDE) {
log_info(
"launcher",
"user specified -cmdoverride, therefore command line args took precedence over spicecfg");
} else {
log_warning(
"launcher",
"spicecfg values take precedence over command line args; to change this behavior, use -cmdoverride");
}
}
if (options[launcher::Options::GameExecutable].is_active() && !cfg::CONFIGURATOR_STANDALONE) {
log_warning(
"launcher",
"WARNING - user specified -exec option\n\n\n"
"!!! !!!\n"
"!!! Using -exec option disables all game-specific hooks! !!!\n"
"!!! Unless you know exactly what you are doing, clear -exec !!!\n"
"!!! and try again. Using -exec by itself will result in weird !!!\n"
"!!! errors and loss of functionality. !!!\n"
"!!! !!!\n"
);
}
if (options[launcher::Options::FullscreenResolution].is_active()) {
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(options[launcher::Options::FullscreenResolution].value_text(), result)) {
@@ -1170,19 +1326,32 @@ int main_implementation(int argc, char *argv[]) {
}
}
if (options[launcher::Options::SDVXFullscreenLandscape].value_bool() && !GRAPHICS_WINDOWED) {
#if SPICE64
GRAPHICS_FS_ORIENTATION_SWAP = true;
#else
log_warning("launcher", "-sdvxlandscape is not supported in 32-bit SDVX, ignoring...");
#endif
// SDVXFullscreenLandscape disabled due to it messing with in-game camera angle
// FullscreenOrientationFlip continues to live on, however.
if (options[launcher::Options::SDVXFullscreenLandscape].value_bool() && !cfg::CONFIGURATOR_STANDALONE) {
log_fatal("launcher", "-sdvxlandscape removed due to a camera bug in SDVX!");
}
// if (options[launcher::Options::SDVXFullscreenLandscape].value_bool() && !GRAPHICS_WINDOWED) {
// #if SPICE64
// GRAPHICS_FS_ORIENTATION_SWAP = true;
// #else
// log_warning("launcher", "-sdvxlandscape is not supported in 32-bit SDVX, ignoring...");
// #endif
// }
if (options[launcher::Options::FullscreenOrientationFlip].value_bool() && !GRAPHICS_WINDOWED) {
GRAPHICS_FS_ORIENTATION_SWAP = true;
}
if (games::iidx::DISABLE_CAMS.has_value() &&
games::iidx::DISABLE_CAMS.value() &&
!cfg::CONFIGURATOR_STANDALONE) {
log_misc("launcher", "CONNECT_CAMERA env var set to 0");
SetEnvironmentVariable("CONNECT_CAMERA", "0");
}
// deleted options
if (options[launcher::Options::OpenKFControl].value_bool()) {
if (options[launcher::Options::OpenKFControl].value_bool() && !cfg::CONFIGURATOR_STANDALONE) {
log_fatal("launcher", "KFControl has been removed from spice2x; please use an older version.");
}
if (options[launcher::Options::VREnable].value_bool()) {
@@ -1239,7 +1408,11 @@ int main_implementation(int argc, char *argv[]) {
log_info("launcher", "loading early hook DLL {}", hook);
HMODULE module;
if (!(module = libutils::try_library(hook))) {
log_warning("launcher", "failed to load early hook {}", hook);
log_warning("launcher", "failed to load early hook {}: {}", hook, get_last_error_string());
deferredlogs::defer_error_messages({
fmt::format("failed to load early hook {}:", hook),
fmt::format(" {}", get_last_error_string())
});
}
}
@@ -1506,6 +1679,14 @@ int main_implementation(int argc, char *argv[]) {
attach_io = true;
attach_mfc = true;
break;
// Mahjong Fight Club - 2025?
// they removed allinone.dll and moved i/o into system.dll
} else if (check_dll("system.dll") && fileutils::file_exists("data/mfc.ini")) {
avs::game::DLL_NAME = "system.dll";
attach_io = true;
attach_mfc = true;
break;
}
// FutureTomTom
@@ -1616,6 +1797,16 @@ int main_implementation(int argc, char *argv[]) {
break;
}
// Polaris Chord
if (check_dll("kamunity.dll") && fileutils::dir_exists("game/svm_Data")) {
avs::game::DLL_NAME = "kamunity.dll";
attach_io = true;
attach_pc = true;
launcher::signal::USE_VEH_WORKAROUND = true;
show_cursor_if_no_touch = true;
break;
}
// Busou Shinki: Armored Princess Battle Conductor
if (check_dll("kamunity.dll") && fileutils::file_exists("game/bsac_app.exe")) {
avs::game::DLL_NAME = "kamunity.dll";
@@ -1640,7 +1831,16 @@ int main_implementation(int argc, char *argv[]) {
// usage error
if (!cfg::CONFIGURATOR_STANDALONE
&& (!CHECK_DLL_IGNORE_ARCH)) {
log_fatal("launcher", "module auto detection failed.");
log_warning(
"launcher",
"module auto detection failed!\n\n"
"This usually means one of the following:\n\n"
" 1. You put spice executables in the wrong place, or\n"
" 2. XML files in prop directory are malformed or missing, or\n"
" 3. You passed in invalid options (usually, path overrides like -exec)\n\n"
);
log_fatal("launcher", "module auto detection failed, see log.txt for troubleshooting");
}
break;
@@ -1768,6 +1968,10 @@ int main_implementation(int argc, char *argv[]) {
avs::core::set_default_heap_size("kamunity.dll");
games.push_back(new games::mfg::MFGGame());
}
if (attach_pc) {
avs::core::HEAP_SIZE = 536870912; // 512MB
games.push_back(new games::pc::PCGame());
}
// apply user heap size, if defined
if (user_heap_size > 0) {
@@ -1800,6 +2004,24 @@ int main_implementation(int argc, char *argv[]) {
exit(spicecfg_run(sextet_devices));
}
// log some DLLs found in path (purely for troubleshooting purposes to detect
// dxvk, ForceD3D9On12, ifs_layeredfs, etc)
libutils::warn_if_dll_exists("d3d8.dll");
libutils::warn_if_dll_exists("d3d9.dll");
libutils::warn_if_dll_exists("d3d10core.dll");
libutils::warn_if_dll_exists("d3d11.dll");
libutils::warn_if_dll_exists("d3d12.dll");
libutils::warn_if_dll_exists("dxgi.dll");
libutils::warn_if_dll_exists("opengl32.dll");
libutils::warn_if_dll_exists("nvcuda.dll");
libutils::warn_if_dll_exists("nvcuvid.dll");
libutils::warn_if_dll_exists("nvEncodeAPI64.dll");
libutils::warn_if_dll_exists("msvcr100.dll");
libutils::warn_if_dll_exists("dsound.dll");
// complain loudly & early about dll load ordering issue
libutils::check_duplicate_dlls();
// print cpu features
if (!cfg::CONFIGURATOR_STANDALONE && dump_sysinfo) {
cpuutils::print_cpu_features();
@@ -1817,6 +2039,21 @@ int main_implementation(int argc, char *argv[]) {
// print devices
RI_MGR->devices_print();
auto buttons = games::get_buttons(eamuse_get_game());
log_misc("rawinput", "Button mappings:");
dump_button_bindings(buttons);
log_misc("rawinput", "Keypad button mappings:");
auto keypads = games::get_buttons_keypads(eamuse_get_game());
dump_button_bindings(keypads);
log_misc("rawinput", "Overlay button mappings:");
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
dump_button_bindings(overlay_buttons);
log_misc("rawinput", "Analog mappings:");
dump_analog_bindings();
// for certain games, show cursor if no touch is available (must be called after RI_MGR is available)
if (show_cursor_if_no_touch && !is_touch_available("launcher::main_implementation")) {
GRAPHICS_SHOW_CURSOR = true;
@@ -1958,15 +2195,6 @@ int main_implementation(int argc, char *argv[]) {
networkhook_init();
}
// layeredfs
if (fileutils::dir_exists("data_mods") &&
!fileutils::file_exists("ifs_hook.dll") &&
!fileutils::file_exists(MODULE_PATH / "ifs_hook.dll")) {
log_warning("launcher", "data_mods directory was found, but ifs_hook.dll is not present; your mods will not load");
log_warning("launcher", "to fix this, download ifs_layeredfs and add it as a DLL hook (-k)");
log_warning("launcher", "https://github.com/mon/ifs_layeredfs");
}
update_msvcrt_args(argc, argv);
// load hooks
@@ -1974,12 +2202,28 @@ int main_implementation(int argc, char *argv[]) {
log_info("launcher", "loading hook DLL {}", hook);
HMODULE module;
if (!(module = libutils::try_library(hook))) {
log_warning("launcher", "failed to load hook {}", hook);
log_warning("launcher", "failed to load hook {}: {}", hook, get_last_error_string());
deferredlogs::defer_error_messages({
fmt::format("failed to load hook {}:", hook),
fmt::format(" {}", get_last_error_string())
});
} else {
bt5api_hook(module);
}
}
// layeredfs
if (fileutils::dir_exists("data_mods") &&
GetModuleHandleA("ifs_hook.dll") == nullptr) {
log_warning("launcher", "data_mods directory found, but ifs_hook.dll is not present; mods will not load");
deferredlogs::defer_error_messages({
"data_mods directory was found, but ifs_hook.dll is not present",
" your mods will not load",
" to fix this, download ifs_layeredfs and add it as a DLL hook (-k)",
" https://github.com/mon/ifs_layeredfs"
});
}
// apply patches
{
overlay::windows::PatchManager patch_manager(nullptr, true);
@@ -2112,8 +2356,10 @@ int main_implementation(int argc, char *argv[]) {
// debug hook
debughook::detach();
#ifndef NO_SCARD
// scard
scard_fini();
#endif
// stop reader thread in case it was running
stop_reader_thread();
@@ -2202,6 +2448,65 @@ void update_msvcrt_args(int argc, char *argv[]) {
#endif
}
void dump_button_bindings(std::vector<Button> *buttons) {
if (!buttons) {
return;
}
for (auto button = buttons->begin(); button != buttons->end(); ++button) {
if (!button->isSet()) {
continue;
}
if (button->isNaive()) {
log_misc(
"rawinput", " [{}] dev=Naive, vkey={}",
button->getName(),
button->getVKey()
);
} else {
log_misc(
"rawinput", " [{}] dev={}, vkey={}, analogtype={}",
button->getName(),
button->getDeviceIdentifier(),
button->getVKey(),
button->getAnalogType()
);
}
for (auto& alt : button->getAlternatives()) {
if (alt.getVKey() == INVALID_VKEY) {
continue;
}
log_misc(
"rawinput", " [{}] (alt) dev={}, vkey={}, analogtype={}",
button->getName(),
alt.isNaive() ? "Naive" : alt.getDeviceIdentifier(),
alt.getVKey(),
alt.getAnalogType()
);
}
}
}
void dump_analog_bindings() {
auto analogs = games::get_analogs(eamuse_get_game());
if (!analogs) {
return;
}
for (auto& analog : *analogs) {
if (!analog.isSet()) {
continue;
}
log_misc(
"rawinput", " [{}] dev={}, index={}",
analog.getName(),
analog.getDeviceIdentifier(),
analog.getIndex()
);
}
}
#ifndef SPICETOOLS_SPICECFG_STANDALONE
int main(int argc, char *argv[]) {
return main_implementation(argc, argv);
+381 -89
View File
@@ -6,6 +6,12 @@
#include <fstream>
#ifdef NO_SCARD
#define WITH_SCARD 0
#else
#define WITH_SCARD 1
#endif
static const std::vector<std::string> CATEGORY_ORDER_API = {
"SpiceCompanion and API",
"API (Serial)",
@@ -17,7 +23,6 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
"Game Options",
"Common",
"Network",
"Overlay",
"Graphics (Common)",
"Graphics (Full Screen)",
"Graphics (Windowed)",
@@ -26,35 +31,42 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
static const std::vector<std::string> CATEGORY_ORDER_ADVANCED = {
"Game Options (Advanced)",
"Overlay",
"Network (Advanced)",
"Performance",
"Miscellaneous",
"Paths",
"Touch Parameters",
"I/O Options",
"NFC Card Readers",
};
static const std::vector<std::string> CATEGORY_ORDER_DEV = {
"Network Adapters",
"Path Overrides",
"Network (Development)",
"Audio (Hacks)",
"I/O Modules",
"Development",
"Debug Log",
};
static const std::vector<std::string> CATEGORY_ORDER_NONE = {
""
};
bool launcher::USE_CMD_OVERRIDE = false;
/*
* Option Definitions
* Be aware that the order must be the same as in the enum launcher::Options!
*/
static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
{
.title = "Game Executable",
.title = "Override Game Executable & Disable Hooks",
.name = "exec",
.desc = "Path to the game DLL file",
.desc = "Path to the game DLL file.\n\n"
"This option is typically not needed; leave it empty to allow spice to auto-detect your game.\n\n"
"WARNING: this option also disables all game-specific hooks and turns off most I/O modules!",
.type = OptionType::Text,
.setting_name = "*.dll",
.category = "Paths",
.category = "Path Overrides",
},
{
.title = "Open Configurator",
@@ -81,6 +93,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Network",
},
{
// ServiceURL
.title = "EA Service URL",
.name = "url",
.desc = "Sets a custom service URL override",
@@ -142,6 +155,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Graphics (Windowed)",
},
{
// InjectHook
.title = "Inject DLL Hooks",
.name = "k",
.desc = "Multiple files are allowed; use multiple -k flags, or in SpiceCfg, separate by "
@@ -152,6 +166,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Common",
},
{
// EarlyInjectHook
.title = "Inject Early DLL Hooks",
.name = "z",
.desc = "Equivalent to 'Inject DLL Hooks' option, but ensures hooks are injected before "
@@ -228,7 +243,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc =
"Allows you to play portrait games in in landscape (and vice versa) by transposing resolution and applying image scaling.\n\n"
"Works great for some games, but can COMPLETELY BREAK other games - YMMV!\n\n"
"Strongly consider combining this with -forceres option to render at monitor native resolution",
"Strongly consider combining this with -forceres option to render at monitor native resolution\n\n"
"WARNING: for SDVX, this messes with camera angle for note lanes, causing it to be zoomed out!",
.type = OptionType::Bool,
.category = "Graphics (Full Screen)"
},
@@ -262,7 +278,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.name = "nolegacy",
.desc = "Disables legacy key activation in-game.",
.type = OptionType::Bool,
.category = "Miscellaneous",
.category = "I/O Options",
},
{
.title = "Discord Rich Presence",
@@ -304,28 +320,29 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "This is NOT the EA service URL; use -url for that. "
"Force the use of an adapter with the specified network. Must also provide -subnet",
.type = OptionType::Text,
.category = "Network Adapters",
.category = "Network (Development)",
.sensitive = true,
},
{
.title = "Preferred NetAdapter Subnet",
.name = "subnet",
.desc = "Force the use of an adapter with the specified subnet. Must also provide -network",
.type = OptionType::Text,
.category = "Network Adapters",
.category = "Network (Development)",
},
{
.title = "Disable Network Fixes",
.name = "netfixdisable",
.desc = "Force disables network fixes",
.type = OptionType::Bool,
.category = "Network Adapters",
.category = "Network (Development)",
},
{
.title = "HTTP/1.1",
.name = "http11",
.desc = "Sets EA3 http11 value",
.type = OptionType::Enum,
.category = "Network (Advanced)",
.category = "Network (Development)",
.elements = {{"0", "Off"}, {"1", "On"}},
},
{
@@ -333,7 +350,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.name = "ssldisable",
.desc = "Prevents the SSL protocol from being registered",
.type = OptionType::Bool,
.category = "Network (Advanced)",
.category = "Network (Development)",
},
{
.title = "URL Slash",
@@ -348,7 +365,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.name = "r",
.desc = "Set custom SOFTID override",
.type = OptionType::Text,
.category = "Network (Advanced)",
.category = "Development",
.sensitive = true,
},
{
@@ -439,13 +456,30 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options (Advanced)",
},
{
.title = "IIDX Disable Cameras",
// IIDXDisableCameras
.title = "IIDX Disable Cameras (DEPRECATED - no longer needed)",
.name = "iidxdisablecams",
.desc = "Disables cameras",
.type = OptionType::Bool,
.hidden = true,
.game_name = "Beatmania IIDX",
.category = "Game Options",
},
{
// IIDXCabCamAccess
.title = "IIDX Use Official AC Cams",
.name = "iidxcabcams",
.desc = "For IIDX25+, allow direct access to cameras from real arcade cabinets. "
"Only turn this on if you have OFFICIAL arcade cameras connected to the correct USB ports. Default: auto",
.type = OptionType::Enum,
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
.elements = {
{"auto", ""},
{"off", ""},
{"on", ""}
},
},
{
// IIDXCamHook
.title = "IIDX Cam Hook",
@@ -480,10 +514,10 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.display_name = "iidxcamhookoffset",
.aliases = "iidxcamhookoffset",
.desc = "Override DLL offsets for camera hook; "
"format: 4 hex values separated by commas "
"(offset_hook_a, offset_hook_b, offset_textures, offset_d3d_device)",
"format: 5 hex values separated by commas "
"(offset_hook_a, offset_textures, offset_camera_manager, offset_d3d_device, offset_afp_texture)",
.type = OptionType::Text,
.setting_name = "0x5817a0,0x5ea3b0,0x6fffbd8,0xe0",
.setting_name = "0x5817a0,0x6fffbd8,0xbbae40,0xe0,0x30",
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
},
@@ -543,10 +577,11 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options",
},
{
.title = "IIDX BIO2 Firmware",
.title = "IIDX BIO2 Firmware Update",
.name = "iidxbio2fw",
.desc = "Enables BIO2 firmware updates",
.desc = "Enables BIO2 firmware updates. WARNING - can cause semi-permanent change to your I/O board",
.type = OptionType::Bool,
.hidden = true,
.game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)",
},
@@ -666,6 +701,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options (Advanced)",
},
{
// SDVXPrinterOutputPath
.title = "SDVX Printer Output Path",
.name = "printerpath",
.desc = "Path to folder where images will be stored",
@@ -690,9 +726,10 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options (Advanced)",
},
{
// SDVXPrinterOutputFormat
.title = "SDVX Printer Output Format",
.name = "printerformat",
.desc = "Path to folder where images will be stored",
.desc = "File format for printer output",
.type = OptionType::Text,
.setting_name = "(png/bmp/tga/jpg)",
.game_name = "Sound Voltex",
@@ -708,10 +745,13 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options (Advanced)",
},
{
.title = "SDVX Disable Cameras",
.title = "SDVX Disable Cameras (DEPRECATED - no longer needed)",
.name = "sdvxdisablecams",
.desc = "Disables cameras",
.desc = "This option does nothing.\n\n"
"This option was used in the past to fix cameras in SDVX5 but this is no longer "
"needed as camera check will always be skipped with a built-in patch.",
.type = OptionType::Bool,
.hidden = true,
.game_name = "Sound Voltex",
.category = "Game Options",
},
@@ -797,6 +837,15 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Dance Dance Revolution",
.category = "Game Options",
},
{
// DDRSkipCodecRegisteration
.title = "DDR Skip Codec Registration",
.name = "ddrnocodec",
.desc = "Prevent automatic registration of codecs in the com folder",
.type = OptionType::Bool,
.game_name = "Dance Dance Revolution",
.category = "Game Options (Advanced)",
},
{
.title = "Force Load Pop'n Music Module",
.name = "pnm",
@@ -1024,7 +1073,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
},
{
// LoadCCJModule
.title = "Force Load Chase Chase Jokers",
.title = "Force Load Chase Chase Jokers Module",
.name = "ccj",
.desc = "manually enable Chase Chase Jokers module",
.type = OptionType::Bool,
@@ -1033,7 +1082,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
},
{
// LoadQKSModule
.title = "Force Load QuizKnock STADIUM",
.title = "Force Load QuizKnock STADIUM Module",
.name = "qks",
.desc = "manually enable QuizKnock STADIUM module",
.type = OptionType::Bool,
@@ -1042,16 +1091,25 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
},
{
// LoadMFGModule
.title = "Force Load Mahjong Fight Girl",
.title = "Force Load Mahjong Fight Girl Module",
.name = "mfg",
.desc = "manually enable Mahjong Fight Girl module",
.type = OptionType::Bool,
.game_name = "Mahjong Fight Girl",
.category = "Game Options (Advanced)",
},
{
// LoadPCModule
.title = "Force Load Polaris Chord Module",
.name = "pc",
.desc = "manually enable Polaris Chord module",
.type = OptionType::Bool,
.game_name = "Polaris Chord",
.category = "Game Options (Advanced)",
},
{
// LoadMusecaModule
.title = "Force Load Museca",
.title = "Force Load Museca Module",
.name = "museca",
.desc = "manually enable Museca module",
.type = OptionType::Bool,
@@ -1059,88 +1117,88 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options (Advanced)",
},
{
.title = "Modules Folder Path",
.title = "Modules Folder Override",
.name = "modules",
.desc = "Sets a custom path to the modules folder",
.type = OptionType::Text,
.category = "Paths",
.category = "Path Overrides",
},
{
.title = "Screenshot Folder Path",
.title = "Screenshot Folder Override",
.name = "screenshotpath",
.desc = "Sets a custom path to the screenshots folder",
.type = OptionType::Text,
.category = "Paths",
.category = "Path Overrides",
},
{
.title = "Configuration Path",
.title = "Configuration Path Override",
.name = "cfgpath",
.desc = "Sets a custom file path for config file. Must be passed via the command line. "
"If left empty, %appdata%\\spicetools.xml will be used",
.type = OptionType::Text,
.setting_name = "(default)",
.category = "Paths",
.category = "Path Overrides",
.disabled = true,
},
{
// ScreenResizeConfigPath
.title = "Screen Resize Config Path",
.title = "Screen Resize Config Path Override",
.name = "resizecfgpath",
.desc = "Sets a custom file path for screen resize config file. "
"If left empty, %appdata%\\spicetools_screen_resize.json will be used",
"If left empty, %appdata%\\spice2x\\spicetools_screen_resize.json will be used",
.type = OptionType::Text,
.category = "Paths",
.category = "Path Overrides",
},
{
// PatchManagerConfigPath
.title = "Patch Manager Config Path",
.title = "Patch Manager Config Path Override",
.name = "patchcfgpath",
.desc = "Sets a custom file path for patch manager config file. Can be used to manage 'profiles' for auto-patches. "
"If left empty, %appdata%\\spicetools_patch_manager.json will be used",
"If left empty, %appdata%\\spice2x\\spicetools_patch_manager.json will be used",
.type = OptionType::Text,
.category = "Paths",
.category = "Path Overrides",
},
{
.title = "Intel SDE Folder Path",
.title = "Intel SDE",
.name = "sde",
.desc = "Path to Intel SDE kit path for automatic attaching",
.type = OptionType::Text,
.category = "Paths",
.category = "Development",
},
{
.title = "Path to ea3-config.xml",
.title = "ea3-config.xml Override",
.name = "e",
.desc = "Sets a custom path to ea3-config.xml",
.type = OptionType::Text,
.category = "Paths",
.category = "Path Overrides",
},
{
.title = "Path to app-config.xml",
.title = "app-config.xml Override",
.name = "a",
.desc = "Sets a custom path to app-config.xml",
.type = OptionType::Text,
.category = "Paths",
.category = "Path Overrides",
},
{
.title = "Path to avs-config.xml",
.title = "avs-config.xml Override",
.name = "v",
.desc = "Sets a custom path to avs-config.xml",
.type = OptionType::Text,
.category = "Paths",
.category = "Path Overrides",
},
{
.title = "Path to bootstrap.xml",
.title = "bootstrap.xml Override",
.name = "b",
.desc = "Sets a custom path to bootstrap.xml",
.type = OptionType::Text,
.category = "Paths",
.category = "Path Overrides",
},
{
.title = "Path to log.txt",
.title = "log.txt Override",
.name = "y",
.desc = "Sets a custom path to log.txt",
.type = OptionType::Text,
.category = "Paths",
.category = "Path Overrides",
},
{
.title = "API TCP Port",
@@ -1192,6 +1250,28 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool,
.category = "API (Dev)",
},
{
// APIScreenMirrorQuality
.title = "API Screen Mirror Quality Override",
.name = "apiscreenq",
.desc = "JPEG compression level of mirrored images, overriding any client request. "
"Value must be between 0 (poor quality) and 100 (best quality), inclusive. Default: 70",
.type = OptionType::Integer,
.setting_name = "(0-100)",
.category = "SpiceCompanion and API",
},
{
// APIScreenMirrorDivide
.title = "API Screen Mirror Divide Override",
.name = "apiscreendiv",
.desc = "Divide value of mirrored images, overriding any client request. "
"Divide of 1 means send every pixel, 2 means every other pixel, 3 means every third pixel, and so on; "
"increasing the value drastically reduces transfer size at the cost of image quality. "
"Value must be 1 or greater. Default: 1",
.type = OptionType::Integer,
.setting_name = "1",
.category = "SpiceCompanion and API",
},
{
.title = "Enable All IO Modules",
.name = "io",
@@ -1287,6 +1367,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Touch Parameters",
},
{
// ICCAReaderPort
.title = "ICCA Reader Port",
.name = "reader",
.desc = "Connects to and uses a ICCA on a given COM port",
@@ -1327,21 +1408,27 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.name = "scard",
.desc = "Detects and uses HID smart card readers for card input. Developed for ACR122U reader",
.type = OptionType::Bool,
.hidden = !WITH_SCARD,
.category = "NFC Card Readers",
.disabled = !WITH_SCARD,
},
{
.title = "HID SmartCard Order Flip",
.name = "scardflip",
.desc = "Flips the order of detection for P1/P2",
.type = OptionType::Bool,
.hidden = !WITH_SCARD,
.category = "NFC Card Readers",
.disabled = !WITH_SCARD,
},
{
.title = "HID SmartCard Order Toggle",
.name = "scardtoggle",
.desc = "Toggles reader between P1/P2 using the NumLock key state",
.type = OptionType::Bool,
.hidden = !WITH_SCARD,
.category = "NFC Card Readers",
.disabled = !WITH_SCARD,
},
{
// HIDSmartCardIdConvert
@@ -1355,19 +1442,22 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"For example, abcd56780123 is converted to e00401abcd567801 on card in. This results in different card numbers so "
"be careful when connecting to servers!",
.type = OptionType::Enum,
.hidden = !WITH_SCARD,
.category = "NFC Card Readers",
.elements = {
{"legacy", "Use cards as-is"},
{"fix", "Fix bad cards only"},
{"all", "Force all cards"},
},
.disabled = !WITH_SCARD,
},
{
// SextetStreamPort
.title = "SextetStream Port",
.name = "sextet",
.desc = "Use a SextetStream device on a given COM port",
.type = OptionType::Text,
.category = "Miscellaneous",
.category = "I/O Options",
},
{
.title = "Enable BemaniTools 5 API",
@@ -1468,7 +1558,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"you use a different backend instead of exclusive WASAPI).\n\n"
"Check this if you want games to natively access your audio device",
.type = OptionType::Bool,
.category = "Audio",
.category = "Audio (Hacks)",
},
{
// spice2x_DisableVolumeHook
@@ -1480,16 +1570,19 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"Default: off (prevent games from changing audio volume by hooking IAudioEndpointVolume).\n\n"
"Check this if you want games to freely change your volume",
.type = OptionType::Bool,
.category = "Audio",
.category = "Audio (Hacks)",
},
{
.title = "Spice Audio Hook Backend",
.name = "audiobackend",
.desc = "Selects the audio backend to use when spice audio hook is enabled, overriding exclusive WASAPI. "
" Does nothing for games that do not output to exclusive WASAPI",
"Does nothing for games that do not output to exclusive WASAPI",
.type = OptionType::Enum,
.category = "Audio",
.elements = {{"asio", "ASIO"}, {"waveout", "waveOut"}},
.elements = {
{"asio", "ASIO"},
{"waveout", "broken, do not use"}
},
},
{
.title = "Spice Audio Hook ASIO Driver ID",
@@ -1505,7 +1598,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"This is automatically enabled when required and not normally needed",
.type = OptionType::Bool,
.hidden = true,
.category = "Audio",
.category = "Audio (Hacks)",
},
{
// DelayBy5Seconds
@@ -1558,11 +1651,11 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.elements = {{"0", "90 (CW)"}, {"1", "270 (CCW)"}},
},
{
.title = "Log Level",
.title = "AVS Log Level",
.name = "loglevel",
.desc = "Set the level of detail that gets written to the log",
.desc = "Set the level of detail for AVS log messages written to the log. Does not affect logging from spice",
.type = OptionType::Enum,
.category = "Performance",
.category = "Debug Log",
.elements = {{"fatal", ""}, {"warning", ""}, {"info", ""}, {"misc", ""}, {"all", ""}, {"disable", ""}},
},
{
@@ -1570,14 +1663,14 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.name = "automap",
.desc = "Enable automap in patch configuration",
.type = OptionType::Bool,
.category = "Development",
.category = "Network (Development)",
},
{
.title = "EA Netdump",
.name = "netdump",
.desc = "Enable automap in network dumping configuration",
.type = OptionType::Bool,
.category = "Development",
.category = "Network (Development)",
},
{
.title = "Discord RPC AppID Override",
@@ -1591,35 +1684,45 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.name = "logblock",
.desc = "Slower but safer logging used for debugging",
.type = OptionType::Bool,
.category = "Development",
.category = "Debug Log",
},
{
.title = "Debug CreateFile",
.name = "createfiledebug",
.desc = "Outputs CreateFile debug prints",
.type = OptionType::Bool,
.category = "Development",
.category = "Debug Log",
},
{
.title = "Verbose Graphics Logging",
.name = "graphicsverbose",
.desc = "Enable the verbose logging of graphics hook code",
.type = OptionType::Bool,
.category = "Development",
.category = "Debug Log",
},
{
.title = "Verbose AVS Logging",
.name = "avsverbose",
.desc = "Enable the verbose logging of AVS filesystem functions",
.type = OptionType::Bool,
.category = "Development",
.category = "Debug Log",
},
{
// AllowEA3Verbose
.title = "Allow EA3 Verbose Debug Logging",
.name = "ea3verbose",
.desc = "This option is only useful for network developers; leave this OFF.\n\n"
"By default, ea3/debug/verbose node is removed by spice to prevent exposing PCBID in the logs. "
"When this is enabled, ea3/debug/verbose node in the XML will be kept and exposed to the game",
.type = OptionType::Bool,
.category = "Debug Log",
},
{
.title = "Disable Colored Output",
.name = "nocolor",
.desc = "Disable terminal colors for log outputs to console",
.type = OptionType::Bool,
.category = "Development",
.category = "Debug Log",
},
{
.title = "Disable ACP Hook",
@@ -1654,7 +1757,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.name = "pebprint",
.desc = "Prints PEB on startup to console",
.type = OptionType::Bool,
.category = "Development",
.category = "Debug Log",
},
{
// DumpSystemInfo
@@ -1662,7 +1765,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.name = "sysdump",
.desc = "Print system information to the log on startup. Default: basic",
.type = OptionType::Enum,
.category = "Development",
.category = "Debug Log",
.elements = {
{"none", "Nothing"},
{"basic", "OS, CPU, SMBIOS, GPU"},
@@ -1747,6 +1850,35 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Mahjong Fight Girl",
.category = "Game Options (Advanced)"
},
{
// PCArgs
.title = "PC Arguments Override",
.name = "pcargs",
.desc = "Command line arguments passed to the game.",
.type = OptionType::Text,
.setting_name = "",
.game_name = "Polaris Chord",
.category = "Game Options (Advanced)",
},
{
// PCNoIO
.title = "PC Disable IO Emulation",
.name = "pcnoio",
.desc = "Disables BI2X hooks for PC",
.type = OptionType::Bool,
.setting_name = "",
.game_name = "Polaris Chord",
.category = "Game Options (Advanced)"
},
{
// PCKnobMode
.title = "PC Fader Knobs Mode",
.name = "pcknobs",
.desc = "Allows SDVX knobs and IIDX turntables to be bound as Polaris Chord faders",
.type = OptionType::Bool,
.game_name = "Polaris Chord",
.category = "Game Options",
},
{
// spice2x_LightsOverallBrightness
.title = "Lights Brightness Adjustment",
@@ -1841,6 +1973,24 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Beatmania IIDX",
.category = "Graphics (Windowed)",
},
{
// IIDXWindowedSubscreenBorderless
.title = "IIDX Windowed Subscreen Borderless",
.name = "iidxwsubborderless",
.desc = "Remove window decoration from windowed subscreen",
.type = OptionType::Bool,
.game_name = "Beatmania IIDX",
.category = "Graphics (Windowed)",
},
{
// IIDXWindowedSubscreenAlwaysOnTop
.title = "IIDX Windowed Subscreen Always On Top",
.name = "iidxwsubtop",
.desc = "Keep windowed subscreen on top",
.type = OptionType::Bool,
.game_name = "Beatmania IIDX",
.category = "Graphics (Windowed)",
},
{
// spice2x_JubeatLegacyTouch
.title = "JB Legacy Touch Targets",
@@ -1874,7 +2024,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "For certain buggy ASIO drivers, force unload of ASIO driver when audio stream stops. "
"Used for working around ASIO drivers that lock up after force quitting games",
.type = OptionType::Bool,
.category = "Audio",
.category = "Audio (Hacks)",
},
{
// spice2x_IIDXNoESpec
@@ -2049,15 +2199,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
},
{
// SDVXFullscreenLandscape
.title = "SDVX Landscape Mode (SDVX5+)",
.title = "(DISABLED) SDVX Landscape Mode (SDVX5+)",
.name = "sdvxlandscape",
.desc =
"Option HIDDEN and DISABLED due to it affecting in-game camera angle for displaying lanes.\n\n"
"Allows you to play in landscape by transposing resolution and applying image scaling.\n\n"
"Works only for SDVX5 and above! This is identical to -forceresswap.\n\n"
"Will launch at 1080p by default; strongly consider combining this with -forceres option to render at monitor native resolution",
.type = OptionType::Bool,
.hidden = true,
.game_name = "Sound Voltex",
.category = "Game Options"
.category = "Game Options",
},
{
// spice2x_EnableSMXStage
@@ -2126,6 +2278,23 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.setting_name = "20",
.category = "I/O Options",
},
{
// InputRequiresFocus
.title = "Input Requires Focus",
.name = "inputfocus",
.desc =
"Determine input behavior when game window is not in focus.\n\n"
"never: allow input regardless of focus\n"
"naive (default): only naive bindings check for window focus, rawinput binds are always allowed\n"
"always: only process input when window is in focus",
.type = OptionType::Enum,
.category = "I/O Options",
.elements = {
{"never", ""},
{"naive", ""},
{"always", ""},
},
},
{
// NostalgiaPoke
.title = "Nost Screen Poke",
@@ -2150,6 +2319,107 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
{"2", "double buffer"},
{"3", "triple buffer"},
},
},
{
// SDVXWindowedSubscreenSize
.title = "SDVX Windowed Subscreen Size",
.name = "sdvxwsubsize",
.desc = "Size of the subscreen window. Defaults to (1920,1080)",
.type = OptionType::Text,
.setting_name = "1920,1080",
.game_name = "Sound Voltex",
.category = "Graphics (Windowed)",
},
{
// SDVXWindowedSubscreenPosition
.title = "SDVX Windowed Subscreen Position",
.name = "sdvxwsubpos",
.desc = "Initial position of the subscreen window. Defaults to (0,0)",
.type = OptionType::Text,
.setting_name = "0,0",
.game_name = "Sound Voltex",
.category = "Graphics (Windowed)",
},
{
// SDVXWindowedSubscreenBorderless
.title = "SDVX Windowed Subscreen Borderless",
.name = "sdvxwsubborderless",
.desc = "Remove window decoration from windowed subscreen",
.type = OptionType::Bool,
.game_name = "Sound Voltex",
.category = "Graphics (Windowed)",
},
{
// SDVXWindowedSubscreenAlwaysOnTop
.title = "SDVX Windowed Subscreen Always On Top",
.name = "sdvxwsubtop",
.desc = "Keep windowed subscreen on top",
.type = OptionType::Bool,
.game_name = "Sound Voltex",
.category = "Graphics (Windowed)",
},
{
// LovePlusCamEnable
.title = "LovePlus Camera Enable",
.name = "lovepluscam",
.desc = "Allow game to access camera; camera must be compatible with game",
.type = OptionType::Bool,
.game_name = "LovePlus",
.category = "Game Options",
},
{
// LovePlusPrinterOutputPath
.title = "LovePlus Printer Output Path",
.name = "lpprinterpath",
.desc = "Path to folder where images will be stored",
.type = OptionType::Text,
.game_name = "LovePlus",
.category = "Game Options (Advanced)",
},
{
.title = "LovePlus Printer Output Clear",
.name = "lpprinterclear",
.desc = "Clean up saved images in the output directory on startup",
.type = OptionType::Bool,
.game_name = "LovePlus",
.category = "Game Options (Advanced)",
},
{
.title = "LovePlus Printer Output Overwrite",
.name = "lpprinteroverwrite",
.desc = "Always overwrite the same file in output directory",
.type = OptionType::Bool,
.game_name = "LovePlus",
.category = "Game Options (Advanced)",
},
{
// LovePlusPrinterOutputFormat
.title = "LovePlus Printer Output Format",
.name = "lpprinterformat",
.desc = "File format for printer output",
.type = OptionType::Text,
.setting_name = "(png/bmp/tga/jpg)",
.game_name = "LovePlus",
.category = "Game Options (Advanced)",
},
{
.title = "LovePlus Printer JPG Quality",
.name = "lpprinterjpgquality",
.desc = "Quality setting in percent if JPG format is used",
.type = OptionType::Integer,
.setting_name = "(0-100)",
.game_name = "LovePlus",
.category = "Game Options (Advanced)",
},
{
// OptionConflictResolution
.title = "Command Line Args Override",
.name = "cmdoverride",
.desc = "By default, option values in spicecfg take precedence over command-line args, for legacy compat.\n"
"When this is specified in command line, command-line args take precedence instead",
.type = OptionType::Bool,
.category = "Development",
.disabled = true,
}
};
@@ -2231,7 +2501,7 @@ std::unique_ptr<std::vector<Option>> launcher::parse_options(int argc, char *arg
} else {
// validate it is an integer
try {
std::stoull(argv[i], nullptr, 16);
auto _ = std::stoull(argv[i], nullptr, 16);
} catch (const std::exception &ex) {
log_fatal("options", "parameter for -{} is not a hex number: {}", alias, argv[i]);
}
@@ -2332,12 +2602,25 @@ std::vector<Option> launcher::merge_options(
auto &new_option = merged.emplace_back(option.get_definition(), "");
new_option.disabled = true;
for (auto &value : option.values()) {
new_option.value_add(value);
}
for (auto &value : override.values()) {
new_option.value_add(value);
if (USE_CMD_OVERRIDE) {
// command-line arguments take precedence (opt-in)
for (auto &value : override.values()) {
new_option.value_add(value);
}
for (auto &value : option.values()) {
new_option.value_add(value);
}
} else {
// spicecfg options take precedence (default)
// this sucks, but it's the default for legacy spicetools compat
for (auto &value : option.values()) {
new_option.value_add(value);
}
for (auto &value : override.values()) {
new_option.value_add(value);
}
}
new_option.conflicting = true;
} else {
auto &new_option = merged.emplace_back(override.get_definition(), "");
new_option.disabled = true;
@@ -2353,12 +2636,22 @@ std::vector<Option> launcher::merge_options(
}
}
}
return merged;
}
std::string launcher::detect_bootstrap_release_code() {
std::string bootstrap_path = "prop/bootstrap.xml";
std::string launcher::detect_bootstrap_release_code(const std::string& bootstrap_user) {
std::string bootstrap_path;
if (!bootstrap_user.empty()) {
bootstrap_path = bootstrap_user;
} else if (fileutils::file_exists("prop/bootstrap.xml")) {
bootstrap_path = "prop/bootstrap.xml";
}
if (bootstrap_path.empty()) {
log_warning("options", "unable to detect bootstrap.xml file");
return "";
}
// load XML
tinyxml2::XMLDocument bootstrap;
@@ -2381,7 +2674,7 @@ std::string launcher::detect_bootstrap_release_code() {
return "";
}
static launcher::GameVersion detect_gameversion_ident() {
static launcher::GameVersion detect_gameversion_ident(const std::string& bootstrap_user) {
// detect ea3-ident path
std::string ident_path;
@@ -2427,7 +2720,7 @@ static launcher::GameVersion detect_gameversion_ident() {
auto node_ext = node_soft->FirstChildElement("ext");
if (node_ext) {
version.ext = node_ext->GetText();
auto bootstrap_ext = launcher::detect_bootstrap_release_code();
auto bootstrap_ext = launcher::detect_bootstrap_release_code(bootstrap_user);
if (version.ext.size() != 10 && bootstrap_ext.size() == 10) {
version.ext = bootstrap_ext;
} else if (bootstrap_ext.size() == 10) {
@@ -2463,8 +2756,7 @@ static launcher::GameVersion detect_gameversion_ident() {
return launcher::GameVersion();
}
launcher::GameVersion launcher::detect_gameversion(const std::string &ea3_user) {
launcher::GameVersion launcher::detect_gameversion(const std::string &ea3_user, const std::string& bootstrap_user) {
// detect ea3-config path
std::string ea3_path;
if (!ea3_user.empty()) {
@@ -2478,7 +2770,7 @@ launcher::GameVersion launcher::detect_gameversion(const std::string &ea3_user)
}
if (ea3_path.empty()) {
log_warning("options", "unable to detect EA3 configuration file");
return detect_gameversion_ident();
return detect_gameversion_ident(bootstrap_user);
}
// load XML
@@ -2518,7 +2810,7 @@ launcher::GameVersion launcher::detect_gameversion(const std::string &ea3_user)
if (ea3_config.Parse(xml_str.c_str()) != tinyxml2::XML_SUCCESS) {
// if we still failed, give up
log_warning("options", "unable to parse fixed xml");
return detect_gameversion_ident();
return detect_gameversion_ident(bootstrap_user);
}
}
@@ -2549,7 +2841,7 @@ launcher::GameVersion launcher::detect_gameversion(const std::string &ea3_user)
auto node_ext = node_soft->FirstChildElement("ext");
if (node_ext) {
version.ext = node_ext->GetText();
auto bootstrap_ext = launcher::detect_bootstrap_release_code();
auto bootstrap_ext = launcher::detect_bootstrap_release_code(bootstrap_user);
if (version.ext.size() != 10 && bootstrap_ext.size() == 10) {
version.ext = bootstrap_ext;
} else if (bootstrap_ext.size() == 10) {
@@ -2582,5 +2874,5 @@ launcher::GameVersion launcher::detect_gameversion(const std::string &ea3_user)
// error
log_warning("options", "unable to find /ea3/soft/model in {}", ea3_path);
return detect_gameversion_ident();
return detect_gameversion_ident(bootstrap_user);
}
+27 -2
View File
@@ -55,6 +55,7 @@ namespace launcher {
LoadIIDXModule,
IIDXCameraOrderFlip,
IIDXDisableCameras,
IIDXCabCamAccess,
IIDXCamHook,
IIDXCamHookRatio,
IIDXCamHookOverride,
@@ -86,6 +87,7 @@ namespace launcher {
spice2x_SDVXSubRedraw,
LoadDDRModule,
DDR43Mode,
DDRSkipCodecRegisteration,
LoadPopnMusicModule,
PopnMusicForceHDMode,
PopnMusicForceSDMode,
@@ -117,6 +119,7 @@ namespace launcher {
LoadCCJModule,
LoadQKSModule,
LoadMFGModule,
LoadPCModule,
LoadMusecaModule,
PathToModules,
ScreenshotFolder,
@@ -136,6 +139,8 @@ namespace launcher {
APISerialBaud,
APIPretty,
APIDebugMode,
APIScreenMirrorQuality,
APIScreenMirrorDivide,
EnableAllIOModules,
EnableACIOModule,
EnableICCAModule,
@@ -184,6 +189,7 @@ namespace launcher {
DebugCreateFile,
VerboseGraphicsLogging,
VerboseAVSLogging,
AllowEA3Verbose,
DisableColoredOutput,
DisableACPHook,
DisableSignalHandling,
@@ -199,6 +205,9 @@ namespace launcher {
MFGArgs,
MFGCabType,
MFGNoIO,
PCArgs,
PCNoIO,
PCKnobMode,
spice2x_LightsOverallBrightness,
spice2x_WindowBorder,
spice2x_WindowSize,
@@ -207,6 +216,8 @@ namespace launcher {
WindowForceScaling,
spice2x_IIDXWindowedSubscreenSize,
spice2x_IIDXWindowedSubscreenPosition,
IIDXWindowedSubscreenBorderless,
IIDXWindowedSubscreenAlwaysOnTop,
spice2x_JubeatLegacyTouch,
spice2x_RBTouchScale,
spice2x_AsioForceUnload,
@@ -229,8 +240,20 @@ namespace launcher {
IIDXRecQuality,
MidiAlgoVer,
MidiNoteSustain,
InputRequiresFocus,
NostalgiaPoke,
ForceBackBufferCount,
SDVXWindowedSubscreenSize,
SDVXWindowedSubscreenPosition,
SDVXWindowedSubscreenBorderless,
SDVXWindowedSubscreenAlwaysOnTop,
LovePlusCamEnable,
LovePlusPrinterOutputPath,
LovePlusPrinterOutputClear,
LovePlusPrinterOutputOverwrite,
LovePlusPrinterOutputFormat,
LovePlusPrinterJPGQuality,
OptionConflictResolution,
};
enum class OptionsCategory {
@@ -242,6 +265,8 @@ namespace launcher {
};
}
extern bool USE_CMD_OVERRIDE;
const std::vector<std::string> &get_categories(Options::OptionsCategory category);
const std::vector<OptionDefinition> &get_option_definitions();
std::unique_ptr<std::vector<Option>> parse_options(int argc, char *argv[]);
@@ -255,6 +280,6 @@ namespace launcher {
std::string ext;
};
std::string detect_bootstrap_release_code();
GameVersion detect_gameversion(const std::string& ea3_user);
std::string detect_bootstrap_release_code(const std::string& bootstrap_user);
GameVersion detect_gameversion(const std::string& ea3_user, const std::string& bootstrap_user);
}
+2
View File
@@ -40,6 +40,8 @@ namespace richpresence {
{"Busou Shinki: Armored Princess Battle Conductor", "1226739666741366916"},
{"Chase Chase Jokers", "1226739863915593770"},
{"QuizKnock STADIUM", "1226739930328334478"},
{"Mahjong Fight Girl", "1417371188342030468"},
{"Polaris Chord", "1417371592224407552"},
};
// state
+4
View File
@@ -7,6 +7,7 @@
#include "easrv/easrv.h"
#include "rawinput/rawinput.h"
#include "hooks/audio/audio.h"
#include "util/deferlog.h"
#include "util/logging.h"
#include "launcher.h"
@@ -21,6 +22,9 @@ namespace launcher {
// therefore, subsystems need to be guarded against multiple unload attempts
log_info("launcher", "stopping subsystems");
// before shutting down logger, dump any deferred log messages
deferredlogs::dump_to_logger();
// flush/stop logger
logger::stop();
+34 -1
View File
@@ -1,14 +1,16 @@
#include "signal.h"
#include <future>
#include <exception>
#include <string>
#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"
@@ -97,6 +99,34 @@ static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *Exception
// 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) {
@@ -145,6 +175,9 @@ static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *Exception
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");
}
+3 -1
View File
@@ -1,12 +1,14 @@
#pragma once
#include <string>
#include <cstdint>
namespace launcher::signal {
// settings
extern bool DISABLE;
extern bool USE_VEH_WORKAROUND;
//void print_stacktrace();
void attach();
void init();
}
+2
View File
@@ -659,6 +659,8 @@ void eamuse_autodetect_game() {
eamuse_set_game("QuizKnock STADIUM");
else if (avs::game::is_model("VFG"))
eamuse_set_game("Mahjong Fight Girl");
else if (avs::game::is_model("XIF"))
eamuse_set_game("Polaris Chord");
else {
log_warning("eamuse", "unknown game model: {}", avs::game::MODEL);
eamuse_set_game("unknown");
+1 -1
View File
@@ -162,7 +162,7 @@ static void __cdecl cardunit_update() {
bool kb_insert_press = false;
// eamio keypress
kb_insert_press |= eamuse_get_keypad_state(unit) & (1 << EAM_IO_INSERT);
kb_insert_press |= static_cast<bool>(eamuse_get_keypad_state(unit) & (1 << EAM_IO_INSERT));
// update card inserts
if (eamuse_card_insert_consume((int) EXTDEV_CARDUNIT_COUNT, unit) ||
+25
View File
@@ -34,6 +34,7 @@ namespace wintouchemu {
bool FORCE = false;
bool INJECT_MOUSE_AS_WM_TOUCH = false;
bool LOG_FPS = false;
bool ADD_TOUCH_FLAG_PRIMARY = false;
static inline bool is_emu_enabled() {
return FORCE || !is_touch_available("wintouchemu::is_emu_enabled") || GRAPHICS_SHOW_CURSOR;
@@ -187,16 +188,28 @@ namespace wintouchemu {
switch (touch_event->type) {
case TOUCH_DOWN:
if (valid) {
if (ADD_TOUCH_FLAG_PRIMARY) {
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
}
touch_input->dwFlags |= TOUCHEVENTF_DOWN;
}
break;
case TOUCH_MOVE:
if (valid) {
if (ADD_TOUCH_FLAG_PRIMARY) {
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
}
touch_input->dwFlags |= TOUCHEVENTF_MOVE;
}
break;
case TOUCH_UP:
// don't check valid so that this touch ID can be released
if (ADD_TOUCH_FLAG_PRIMARY) {
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
}
touch_input->dwFlags |= TOUCHEVENTF_UP;
break;
}
@@ -238,16 +251,28 @@ namespace wintouchemu {
switch (mouse_state.touch_event) {
case TOUCHEVENTF_DOWN:
if (valid) {
if (ADD_TOUCH_FLAG_PRIMARY) {
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
}
touch_input->dwFlags |= TOUCHEVENTF_DOWN;
}
break;
case TOUCHEVENTF_MOVE:
if (valid) {
if (ADD_TOUCH_FLAG_PRIMARY) {
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
}
touch_input->dwFlags |= TOUCHEVENTF_MOVE;
}
break;
case TOUCHEVENTF_UP:
// don't check valid so that this touch ID can be released
if (ADD_TOUCH_FLAG_PRIMARY) {
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
}
touch_input->dwFlags |= TOUCHEVENTF_UP;
break;
}
+1
View File
@@ -8,6 +8,7 @@ namespace wintouchemu {
extern bool FORCE;
extern bool INJECT_MOUSE_AS_WM_TOUCH;
extern bool LOG_FPS;
extern bool ADD_TOUCH_FLAG_PRIMARY;
void hook(const char *window_title, HMODULE module = nullptr, int delay_in_s=0);
void hook_title_ends(
+15 -2
View File
@@ -12,6 +12,7 @@
#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) || \
@@ -175,10 +176,22 @@ static void ImGui_ImplSpice_UpdateMousePos() {
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 (HWND active_window = ::GetForegroundWindow()) {
if (active_window) {
if (active_window == g_hWnd
|| ::IsChild(active_window, g_hWnd)
|| ::IsChild(g_hWnd, active_window)
@@ -208,7 +221,7 @@ static void ImGui_ImplSpice_UpdateMousePos() {
static size_t delay_touch = 0;
static size_t delay_touch_target = 2;
static DWORD last_touch_id = ~0u;
if (!touch_points.empty()) {
if (!touch_points.empty() && !is_windowed_subscreen) {
// use the first touch point
auto &tp = touch_points[0];
+16 -15
View File
@@ -174,18 +174,18 @@ void overlay::SpiceOverlay::init() {
// https://github.com/ocornut/imgui/issues/707#issuecomment-760220280
// r, g, b, a
ImVec4* colors = ImGui::GetStyle().Colors;
// colors[ImGuiCol_Text] = ImVec4(0.75f, 0.75f, 0.75f, 1.00f);
// colors[ImGuiCol_TextDisabled] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f);
// colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.94f);
// colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.f, 0.f, 0.94f);
// note: also see bg_alpha value in each overlay window
colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 1.0f);
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 1.0f);
colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.00f, 0.50f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.37f, 0.14f, 0.00f, 0.54f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.37f, 0.14f, 0.14f, 0.67f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.39f, 0.20f, 0.20f, 0.67f);
colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.18f, 0.18f, 0.54f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.18f, 0.18f, 0.18f, 0.80f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.24f, 0.24f, 0.24f, 0.80f);
colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.48f, 0.16f, 0.16f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.48f, 0.16f, 0.16f, 1.00f);
@@ -194,16 +194,16 @@ void overlay::SpiceOverlay::init() {
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.56f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.89f, 0.00f, 0.19f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 0.19f, 0.19f, 0.40f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.89f, 0.00f, 0.19f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(1.00f, 0.19f, 0.19f, 0.40f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.80f, 0.17f, 0.00f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.89f, 0.00f, 0.19f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.80f, 0.17f, 0.00f, 0.90f);
colors[ImGuiCol_Header] = ImVec4(0.33f, 0.35f, 0.36f, 0.53f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.76f, 0.28f, 0.44f, 0.67f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.67f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.47f, 0.47f, 0.47f, 0.67f);
colors[ImGuiCol_Separator] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
@@ -228,7 +228,7 @@ void overlay::SpiceOverlay::init() {
colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f);
colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f);
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.04f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.02f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
@@ -270,13 +270,14 @@ void overlay::SpiceOverlay::init() {
ImFontConfig config {};
config.MergeMode = true;
log_misc("overlay", "loading fonts, failures are not fatal");
add_font("simsun.ttc", &config, io.Fonts->GetGlyphRangesChineseSimplifiedCommon());
add_font("arial.ttc", &config, io.Fonts->GetGlyphRangesCyrillic());
add_font("arial.ttf", &config, io.Fonts->GetGlyphRangesCyrillic());
add_font("meiryu.ttc", &config, io.Fonts->GetGlyphRangesJapanese());
add_font("meiryo.ttc", &config, io.Fonts->GetGlyphRangesJapanese());
add_font("gulim.ttc", &config, io.Fonts->GetGlyphRangesKorean());
add_font("cordia.ttc", &config, io.Fonts->GetGlyphRangesThai());
add_font("arial.ttc", &config, io.Fonts->GetGlyphRangesVietnamese());
add_font("cordia.ttf", &config, io.Fonts->GetGlyphRangesThai());
add_font("arial.ttf", &config, io.Fonts->GetGlyphRangesVietnamese());
// add special font
if (avs::game::is_model("LDJ")) {
+1 -1
View File
@@ -45,7 +45,7 @@ namespace overlay {
ImVec2 init_size = ImVec2(0, 0);
ImVec2 size_min = ImVec2(0, 0);
ImVec2 size_max = ImVec2(-1, -1);
float bg_alpha = 0.8f;
float bg_alpha = 0.96f;
Window(SpiceOverlay *overlay);
};
+7 -6
View File
@@ -29,8 +29,11 @@ namespace overlay::windows {
}
this->toggle_button = games::OverlayButtons::ToggleCardManager;
this->config_path = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools_card_manager.json";
if (fileutils::file_exists(this->config_path)) {
bool file_exists = false;
this->config_path =
fileutils::get_config_file_path("cardmanager", "spicetools_card_manager.json", &file_exists);
if (file_exists) {
this->config_load();
}
@@ -434,8 +437,6 @@ namespace overlay::windows {
}
void CardManager::config_load() {
log_info("cardmanager", "loading config");
// clear cards
this->cards.clear();
@@ -566,10 +567,10 @@ namespace overlay::windows {
doc.Accept(writer);
// save to file
if (fileutils::text_write(this->config_path, buffer.GetString())) {
if (fileutils::write_config_file("cardmanager", this->config_path, buffer.GetString())) {
this->config_dirty = false;
} else {
log_warning("cardmanager", "unable to save config file to {}", this->config_path.string());
log_warning("cardmanager", "unable to save config file");
}
}
+77 -130
View File
@@ -16,7 +16,6 @@
#include "avs/ea3.h"
#include "avs/game.h"
#include "launcher/launcher.h"
#include "launcher/shutdown.h"
#include "launcher/options.h"
#include "misc/eamuse.h"
#include "overlay/imgui/extensions.h"
@@ -557,9 +556,16 @@ namespace overlay::windows {
}
// disclaimer
// note: distribution of modified version of this software without providing source is GPLv3 license violation.
ImGui::TextColored(
ImVec4(1, 0.5f, 0.5f, 1.f),
"Do NOT stream or upload game data anywhere public! Support arcades when you can. Thanks.");
"spice2x is free & open source; if you paid money for it, you got scammed.");
if (cfg::CONFIGURATOR_STANDALONE) {
ImGui::SameLine();
if (ImGui::TextLink(PROJECT_URL)) {
launch_shell(PROJECT_URL);
}
}
}
void Config::build_buttons(const std::string &name, std::vector<Button> *buttons, int min, int max) {
@@ -1721,6 +1727,13 @@ namespace overlay::windows {
std::vector<int> analogs_midi_indices;
if (this->analogs_devices_selected >= 0) {
auto device = this->analogs_devices.at(this->analogs_devices_selected);
// add a tooltip to devices selector
if (!device->name.empty()) {
ImGui::SameLine();
ImGui::HelpMarker(device->name.c_str());
}
switch (device->type) {
case rawinput::MOUSE: {
@@ -2030,6 +2043,11 @@ namespace overlay::windows {
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Reset")) {
analog.resetValues();
}
// clean up
ImGui::EndPopup();
}
@@ -2634,17 +2652,16 @@ namespace overlay::windows {
// check hidden option
if (!this->options_show_hidden && option.value.empty()) {
// skip hidden entries
if (definition.hidden) {
continue;
}
// check for game exclusivity
if (!definition.game_name.empty()) {
if (definition.game_name != this->games_selected_name) {
continue;
}
}
// check for game exclusivity
if (!definition.game_name.empty()) {
if (definition.game_name != this->games_selected_name) {
continue;
}
}
@@ -2823,7 +2840,10 @@ namespace overlay::windows {
std::string current_item = option.value_text();
for (auto &element : definition.elements) {
if (element.first == current_item) {
current_item += fmt::format(" ({})", element.second);
if (!element.second.empty()) {
current_item += fmt::format(" ({})", element.second);
}
break;
}
}
if (current_item.empty()) {
@@ -2831,11 +2851,12 @@ namespace overlay::windows {
}
if (ImGui::BeginCombo("##combo", current_item.c_str(), 0)) {
for (auto &element : definition.elements) {
bool selected = current_item == element.first;
std::string label = element.first;
if (!element.second.empty()) {
label += fmt::format(" ({})", element.second);
}
bool selected = current_item == label;
if (ImGui::Selectable(label.c_str(), selected)) {
this->options_dirty = true;
option.value = element.first;
@@ -2906,26 +2927,28 @@ namespace overlay::windows {
}
void Config::build_about() {
#ifndef SPICE_LINUX
ImGui::TextUnformatted(std::string(
"spice2x (a fork of SpiceTools)\r\n"
"=========================\r\n" +
to_string(VERSION_STRING)).c_str());
ImGui::TextUnformatted("");
if (ImGui::Button(PROJECT_URL)) {
launch_shell(PROJECT_URL, nullptr);
}
ImGui::TextUnformatted("");
#else
ImGui::TextUnformatted(std::string(
resutil::load_file_string_crlf(IDR_README) +
"\r\n" +
"Changelog (Highlights):\r\n" +
resutil::load_file_string_crlf(IDR_CHANGELOG)).c_str());
}
"spice2x (a fork of SpiceTools) for Linux\r\n"
"=========================\r\n" +
to_string(VERSION_STRING)).c_str());
#endif
void Config::build_licenses() {
ImGui::TextUnformatted("");
if (ImGui::TextLink(PROJECT_URL)) {
launch_shell(PROJECT_URL);
}
ImGui::TextUnformatted("");
ImGui::TextUnformatted(resutil::load_file_string_crlf(IDR_README).c_str());
ImGui::TextUnformatted("");
ImGui::TextUnformatted("");
ImGui::TextUnformatted("Licenses:");
ImGui::TextUnformatted(resutil::load_file_string_crlf(IDR_LICENSES).c_str());
}
@@ -2978,32 +3001,26 @@ namespace overlay::windows {
void Config::build_menu(int *game_selected) {
bool about_popup = false;
bool licenses_popup = false;
bool shutdown_popup = false;
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.14f, 0.14f, 0.14f, 1.0f));
if (ImGui::BeginMenuBar()) {
// [spice2x]
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 0.f, 0.f, 1.f));
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImVec4(1.f, 0.f, 0.f, 1.f));
ImGui::BeginDisabled(!cfg::CONFIGURATOR_STANDALONE);
if (ImGui::MenuItem("[spice2x]")) {
launch_shell(PROJECT_URL, nullptr);
if (ImGui::BeginMenu("[spice2x]")) {
ImGui::PopStyleColor();
if (ImGui::MenuItem("spice2x.github.io")) {
launch_shell(PROJECT_URL);
}
if (ImGui::MenuItem("About")) {
about_popup = true;
}
ImGui::EndMenu();
} else {
ImGui::PopStyleColor();
}
ImGui::EndDisabled();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::Separator();
// game selector
ImGui::PushItemWidth(MIN(580, MAX(80, ImGui::GetWindowSize().x - 520)));
ImGui::Combo("##game_selector", game_selected, games_names.data(), (int)games_list.size());
ImGui::PopItemWidth();
ImGui::Separator();
// shortcuts
if (ImGui::BeginMenu("Shortcuts", cfg::CONFIGURATOR_STANDALONE)) {
if (ImGui::BeginMenu("Shortcuts")) {
if (ImGui::MenuItem("USB Game Controllers")) {
launch_shell("control.exe", "joy.cpl");
}
@@ -3016,32 +3033,25 @@ namespace overlay::windows {
ImGui::EndMenu();
}
// popup menus
if (ImGui::MenuItem("Licenses")) {
licenses_popup = true;
}
if (ImGui::MenuItem("About")) {
about_popup = true;
}
// power - only active in games
if (ImGui::BeginMenu("Power", !cfg::CONFIGURATOR_STANDALONE)) {
if (ImGui::MenuItem("Restart Game")) {
launcher::restart();
// help
if (ImGui::BeginMenu("Help")) {
if (ImGui::MenuItem("FAQ")) {
launch_shell("https://github.com/spice2x/spice2x.github.io/wiki/Known-issues");
}
if (ImGui::MenuItem("Exit Game")) {
launcher::shutdown();
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
if (ImGui::MenuItem("Shutdown PC")) {
shutdown_popup = true;
if (ImGui::MenuItem("Wiki")) {
launch_shell("https://github.com/spice2x/spice2x.github.io/wiki");
}
ImGui::EndMenu();
}
ImGui::Separator();
// game selector
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.34f, 0.14f, 0.14f, 0.54f));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.34f, 0.14f, 0.14f, 0.54f));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.34f, 0.14f, 0.14f, 0.64f));
ImGui::PushItemWidth(MIN(700, MAX(100, ImGui::GetWindowSize().x - 400)));
ImGui::Combo("##game_selector", game_selected, games_names.data(), (int)games_list.size());
ImGui::PopItemWidth();
ImGui::PopStyleColor(3);
ImGui::BeginDisabled();
if (!avs::game::is_model("000")) {
@@ -3052,52 +3062,15 @@ namespace overlay::windows {
ImGui::EndMenuBar();
}
ImGui::PopStyleColor(); // ImGuiCol_PopupBg
// workaround for popups triggered by menu, see https://github.com/ocornut/imgui/issues/331
if (about_popup) {
ImGui::OpenPopup("About##topbarpopup");
}
if (licenses_popup) {
ImGui::OpenPopup("Licenses##topbarpopup");
}
if (shutdown_popup) {
ImGui::OpenPopup("System##topbarpopup");
}
// draw popups
{
// unused_open is needed for close button to appear on the popup
bool unused_open = true;
if (ImGui::BeginPopupModal(
"System##topbarpopup",
&unused_open,
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize)) {
static bool force_shutdown = false;
ImGui::Spacing();
ImGui::Checkbox("Force", &force_shutdown);
ImGui::Spacing();
const ImVec2 button_size(100.f, 0.f);
ImGui::Spacing();
if (ImGui::Button("Shutdown PC", button_size)) {
this->shutdown_system(force_shutdown, false);
}
ImGui::Spacing();
if (ImGui::Button("Reboot PC", button_size)) {
this->shutdown_system(force_shutdown, true);
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
if (ImGui::Button("Cancel", button_size)) {
ImGui::CloseCurrentPopup();
}
ImGui::Spacing();
ImGui::EndPopup();
}
const ImVec2 popup_size(
std::min(ImGui::GetIO().DisplaySize.x * 0.9f, 800.f),
std::min(ImGui::GetIO().DisplaySize.y * 0.9f, 800.f));
@@ -3113,32 +3086,6 @@ namespace overlay::windows {
this->build_about();
ImGui::EndPopup();
}
ImGui::SetNextWindowSize(popup_size, ImGuiCond_Appearing);
ImGui::SetNextWindowPos(popup_pos, ImGuiCond_Appearing);
bool unused_open3 = true;
if (ImGui::BeginPopupModal("Licenses##topbarpopup", &unused_open3)) {
this->build_licenses();
ImGui::EndPopup();
}
}
}
void Config::shutdown_system(bool force, bool reboot_instead) {
if (!acquire_shutdown_privs()) {
return;
}
UINT flags = 0;
if (force) {
flags |= EWX_FORCE;
}
if (reboot_instead) {
flags |= EWX_REBOOT;
} else {
flags |= EWX_SHUTDOWN | EWX_HYBRID_SHUTDOWN;
}
if (!ExitWindowsEx(flags, SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_MINOR_MAINTENANCE)) {
return;
}
launcher::shutdown(0);
}
}
+1 -2
View File
@@ -93,9 +93,8 @@ namespace overlay::windows {
void build_options(
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
void build_about();
void build_licenses();
void build_launcher();
void launch_shell(LPCSTR app, LPCSTR file);
void launch_shell(LPCSTR app, LPCSTR file=nullptr);
static void build_page_selector(int *page);
void build_menu(int *game_selected);

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