Compare commits

...

46 Commits

Author SHA1 Message Date
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
sp2xdev 610984f40e update changelog 2025-04-07 17:04:36 -07:00
sp2xdev 1e271b7352 Merge branch 'main' of https://github.com/spice2x/spice2x.github.io 2025-04-07 17:04:14 -07:00
bicarus-dev 07c78caf01 build: add git commit hash to produced artifact (#297)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Previously, the artifact produced by the CI action was `spice2x.zip`.

With this change, it will be `spice2x-ci-1234567.zip` where 1234567 is
the first 7 chars of the commit hash.

Note that for PRs, github produces a merge commit before queueing the
action, so the commit hash won't match up with the HEAD of a fork. The
goal is to just generate unique-ish filenames so this is not a huge
deal.

## Compiling
## Testing

Action has been run in the fork.
2025-04-07 15:56:46 -07:00
sp2xdev d153209d8a update changelog 2025-04-07 02:14:51 -07:00
bicarus-dev 48186245fe overlay: update UI for card manager (#294)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Change the usage pattern for Card Manager UI.

### Previous behavior

Previously, it was a list of cards that you select, and click a button
to insert as P1 or P2.

This is fine, but users got confused when they pressed `Insert Card`
overlay key and got a different card inserted, or when auto-insert
didn't work as expected.

Additionally, if you play a game without continue (IIDX/DDR for
example), then it's cumbersome to bring up the overlay and click on
insert card every time.

### New behavior

Insert Card overlay now lets you pick a card and *slot* it into P1 or P2
side.

From that point on, the slotted card number is used as the override,
replacing what was previously passed to `-card0` or `-card1`. This means
that `Insert Card` button and auto-insert will pick up the new card and
use that instead.

## Compiling
👍 

## Testing
Tested 1p-only game (popn) and 2p games (DDR, IIDX).
2025-04-07 01:49:07 -07:00
bicarus-dev f7c274591a museca: add Start and 10KEY lights (#296)
## Link to GitHub Issue, if one exists
Fixes #295 

## Description of change
Add missing `Start` and `10KEY` lights output, as see in the test menu.

## Compiling
👍 

## Testing
Seems fine in museca test mode.
2025-04-06 19:25:39 -07:00
ASleepyCat d1a20e58b6 Add CI and improve build system (#293)
## Link to GitHub Issue, if one exists
Fixes #292

## Description of change
This PR adds CI with Github Actions. Runs on every push and pull
request. If a new commit is pushed, any previous runs are cancelled for
that branch.

In total, these optimisations (on a 7800X3D with 32GB 6000MT/s RAM and
an NVMe Gen4 SSD):
* Reduces `spicetools/deps` image size by around 200 MB, and build time
by almost 50%
* Reduces compilation time by around 9%

Changes:
* Pull archlinux image from GHCR so that we don't run into pull limits
from docker.io
* Use `base-devel` archlinux image
* Reduces image build time from 141 seconds to 109 seconds on my machine
  * Clean up unneeded packages
* Small reduction in `spicetools/deps` image size (4.35 GB vs 4.14 GB)
* Use precompiled `yay` binary
* Reduces image build time from 163 seconds to 141 seconds on my machine
* Use `nproc` to get the number of cores instead of parsing
`/proc/cpuinfo` with `awk`
* Use `ninja` to compile
  * Speeds up compile times a little, around 13 seconds on my machine
* Don't run `docker run` in interactive TTY in `build_docker.sh`
  * Github Actions disallows interactive TTYs
  * Still allowed for the batch script
* `time` the compilation process
* Enable BuildKit in the bash script
  * Required for build contexts
* Don't use `USER` instruction
  * Breaks Github Actions, doesn't seem to affect build?
* More info:
https://docs.github.com/en/actions/sharing-automations/creating-actions/dockerfile-support-for-github-actions#user
  * `USER` also seems to cause issues on a mounted ReFS image
* Add executable bits to bash scripts

Future ideas:
* Combine `external/Dockerfile` and `./Dockerfile` into one to
deduplicate images and save disk space
* Fix the weird `/src/src` directory structure in the Dockerfile and
build scripts
* Investigate CMake and ninja optimisations?

## Compiling
Check CI status on my fork.

## Testing
Tested with docker on Windows. Also play tested a bit to make sure the
Github Actions artifact works.
2025-04-06 00:29:02 -07:00
sp2xdev 1e02c31524 update changelog 2025-04-04 22:13:01 -07:00
bicarus-dev 66fab48a5b options: update monitor option text (#291)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Update tooltip for `-monitor` option. Move to `Graphics (Full Screen)`
to reduce clutter.

## Compilin
👍 

## Testing
n/a
2025-04-04 22:09:21 -07:00
bicarus-dev ae06652148 touch: avoid calling is_touch_available too early (#289)
## Link to GitHub Issue, if one exists
Fixes #120 

## Description of change
Don't call `is_touch_available` in launcher before RawInput stack is
initialized. This causes us to incorrectly fall back to the Win7/Win8
touch handler.

The desired behavior is to use rawinput touch by default for all games,
unless overridden by the user via `-wintouch`.

Add a bunch of logging so we can spot who's calling
`is_touch_available`.

As a side effect, a handful of games that previously used Win7/8 touch
will now use RawInput touch instead. If this causes any issues, they can
turn `-wintouch` on to force Win7/8 touch logic again.

## Compiling
👍 

## Testing
Test:

* RB
* -wintouch
* TDJ, UFC subscreen and native touch
* overlay, especially in multi-mon
2025-04-04 21:31:18 -07:00
sp2xdev bd6c8d3f3c add gitignore to root 2025-04-04 17:51:40 -07:00
bicarus-dev e88abec2a6 overlay: remove navigator input (#288)
## Link to GitHub Issue, if one exists
n/a

## Description of change
No one uses this, remove them to reduce clutter. This is in preparation
to migrate from deprecated ImGui IO model to new one, and this is part
of that.

## Compiling
👍 

## Testing
2025-04-03 21:16:48 -07:00
bicarus-dev 6de8b529d2 imgui: update to v1.91.4, make various fixes to overlay (#286)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Update ImGui to `v1.91.4`. We can't update to `v1.91.5` or newer just
yet because impl_spice depends on the old IO model of ImGui which got
removed in `v1.91.5`.

We also can't update the DX9 renderer right now, because at some point
overlay breaks in TDJ (and only in TDJ...)

Enable the new debug logging functionality in ImGui. Add a button in
overlay to toggle debug log window.

Fix some minor bugs spotted by the new ImGui debug log: mismatched API
calls, font loading issues.

## Compiling
👍 

## Testing
In progress.
2025-04-03 19:14:48 -07:00
bicarus-dev 7301cdaa6c imgui: reorganize imgui library files to mirror what's in the imgui releases (#285)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Functionally, no change.

Move ImGui library files around so that they mirror how they are
structured in imgui releases. This will make it easier to migrate to a
future version of ImGui.

Add a handful of static asserts to check that we have the necessary
features enabled / disabled in imgui header.

## Compiling
👍 

## Testing
Tested 32-bit overlay, 64-bit overlay, TDJ, and UFC overlays, and
spicecfg.

Interesting finds during ImGui testing (not part of this PR, but for
future reference when upgrading imgui version):
* Updating dx9 backend to the newest causes TDJ to stop rendering the
overlay completely (fine in UFC or any other game)
* Omitting `IMGUI_USE_BGRA_PACKED_COLOR` causes spicecfg software
renderer to use the wrong color (RGB format parsing error probably)
* At some point - v1.90? the `About` and `Licenses` buttons in spicecfg
stop responding after an upgrade, maybe our improper usage of MenuItem?
* Using the docking branch of ImGui is not required but we should just
stick to it for multi-viewport functionality which we may want to use
later.
2025-04-02 22:56:52 -07:00
bicarus-dev 6969dfcdd1 overlay: migrate configurator UI from ImGui Columns to Tables (#282)
## Link to GitHub Issue, if one exists
n/a

## Description of change
`ImGui::Columns` is a deprecated component with many issues. Migrate
Buttons/Analog/Overlay/Lights/Options... tabs to the new table API.
Clean up some UI elements around it to reduce clutter.

Note: there are still other usages of Columns API (MIDI debug output,
ACIO viewer, window manager) but no one hardly ever sees those.
Eventually they will need to be migrated if ImGui ever drops support for
them.

## Compiling
👍 

## Testing
Testing all games in spicecfg and overlay.
2025-04-02 18:33:39 -07:00
bicarus-dev edaf5a386f overlay: fix mouse cursor getting stuck in wrong shape when leaving and coming back to window (#283)
## Link to GitHub Issue, if one exists
Fixes #27 

## Description of change
In standalone configurator mode, when a mouse cursor moves outside the
window and comes back, it may get stuck in the wrong cursor type (e.g.,
get stuck in horizontal resize mode). This is a side effect of software
rendering we do that doesn't deal nicely with mouse events (i.e., not at
all).

## Compiling
Should be fine.

## Testing
Testing spicecfg, spice -cfg, spice64 -cfg, and in-game overlay.
2025-04-02 18:32:30 -07:00
bicarus-dev 302f08dd57 patcher: show errors on invalid json response from remote (#281)
## Link to GitHub Issue, if one exists
#0

## Description of change
When patcher URL returns an invalid response (as in, cannot be parsed as
JSON), show an error message to user.

Add a "copy text" button to copy the error message.

Fix WarnTooltip so that can handle the case where description is an
empty string for a patch - previously this was just showing as multiple
lines of empty space and then the warning.

## Compiling
It's fine except Docker build currently fails due to cmake version
issue.

## Testing
Tested valid / invalid datecodes and checked against responses.
2025-04-02 18:32:15 -07:00
sp2xdev 68f5a614ef update discord-rpc cmake version 2025-04-01 02:35:41 -07:00
sp2xdev 5db43031f2 fix option descriptions 2025-03-29 01:35:02 -07:00
sp2xdev 873ade757a show patch description in patch warn marker 2025-03-29 00:10:42 -07:00
sp2xdev 3c8dc9de32 overlay: change active option color to green 2025-03-28 23:49:12 -07:00
sp2xdev 4694b59cec update changelog 2025-03-28 20:36:52 -07:00
bicarus-dev 4f1ada7a2f overlay: help tooltip updates (#279)
## Link to GitHub Issue, if one exists
n/a

## Description of change

In Options (and in Advanced/API/Search tabs), remove the help marker
`(?)` and instead add a tooltip to the option name label and the control
widgets like the checkbox, text input, and combo. This is to help with
discoverability as many users failed to discover the `(?)` widget.

Similarly, update the Patches tab to remove `(?)` and add tooltips to
widgets. Patches with warnings `(!)` will continue to show it to
distinguish it from other patches.

Update styling for the tooltip (dark red background -> dark gray) so
that it stands out from other widgets.

Remove the purple option text used for game-specific options.

## Compiling
👍 

## Testing
Tested in spicecfg, and in-game overlay.
2025-03-28 20:34:58 -07:00
bicarus-dev 104a9cbffd graphics: rewrite image scaler (#278)
## Link to GitHub Issue, if one exists
Fixes #276 

## Description of change

Note: this change is not compatible with previous screen resize config;
i.e., if you had the zoom setting dialed into a certain view, after this
change it won't be the same area, so you'll have to set up a scene
again. I think this is a worthwhile cost since the old logic is just
broken in weird ways, and preserving old settings is really tedious math
(for a feature that not many people use). Besides, this change will only
use the new Scenes values from the JSON.

Rewrite the DX9 image scaler logic.

* Previously, the rendering surface was fixed at `4096*4096 px`. Now,
this is relative to the backbuffer dimensions, which depends on the game
& respects user provided `-forceres`.
* Remove "center" option, make it the default.

Above two changes fix the aspect ratio issue (scaling in portrait games
not respecting screen ratio) and things breaking at higher res (1080p
when zoomed out far enough, or when forced to run at 4k resolution for
example)

* Use `scenes` leaf of screen resize JSON config for Scene 1 as well,
completely removing ourselves from being compatible with older spice2x
versions for these values.
* Add additional bounds check before calling `StretchRect` to avoid
users getting stuck with a bad layout, which can kill performance.

## Compiling
🦾 

## Testing
Tested 32 bit (popn / ddr), 64 bit (ldj), portrait and landscape modes
(kfc)
2025-03-28 17:23:01 -07:00
bicarus-dev 45a52cff90 sdvx: landscape mode (#277)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add `SDVX Landscape Mode` option, allowing you to play SDVX in landscape
monitor orientation, with black borders on left and right - similar to
how EAC does it. This is meant to be an alternative to what people do
today, which is launching in windowed mode.

By default, with just `-sdvxlandscape` enabled, EG will launch at
1920x1080 (transposed from the original 1080x1920 resolution).

It is possible to combine with `-forceres` to launch at higher
resolutions. At some point though, when rendering at large resolutions
past 1440p, at some point scaling may break due to #276.

Disabled on 32-bit SDVX since dx9 `CreateDeviceEx` call is required;
`CreateDevice` won't work. For the same reason this isn't being made a
generic option for museca/rb/etc. In theory you can supply
`-sdvxlandscape` to games like IIDX and play landscape games in portrait
mode, although again, scaling my break due to #276.

Also, add new options (bottomleft/right) for subscreen overlay position
so that they don't overlap with the main screen in landscape mode.

## Compiling
🙂

## Testing
Tested a couple versions of VW and EG.
2025-03-26 20:21:05 -07:00
sp2xdev bf79b5d2aa update changelog 2025-03-24 16:10:10 -07:00
sp2xdev e74c60f61d MFG code review cleanup 2025-03-24 16:08:22 -07:00
bicarus-dev 89921e5ec2 graphics: custom full screen resolution (#274)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Adds a new option to forcibly set the full screen resolution.

Same idea as -windowscale.

## Compiling
🔺 

## Testing
Seems to be showing similar results as -windowscale:

* Works GREAT for IIDX/SDVX
* popn launches at correct resolution but only draws a small image (at
native res) - can be scaled with F11 menu
* nostalgia is broken in weird ways, image is drawn correctly but bottom
`credits` text draws at wrong offset, game's wintouch logic broken
(perhaps expectedly)

## Other notes
In theory this could enable "play sdvx in landscape mode" option but the
image scaling doesn't quite work with extreme values.
2025-03-24 16:07:04 -07:00
oleg238948234 1b7ebd6fc2 Add Mahjong Fight Girl Support (#275)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Adds Mahjong Fight Girl Support

## Compiling
+

## Testing
Tested with VFG:J:E:A:2024100800 in HG mode with VFG IO. No
modifications made to data, other than avs-config.xml and
ea3-config.xml. B and C modes don't work because of serial touchscreen.
UKS mode doesn't work because of IO.
2025-03-24 16:06:55 -07:00
sp2xdev 165cc6028f update changelog 2025-03-23 18:15:10 -07:00
bicarus-dev 8dc835bf52 Auto PIN entry (#272)
## Link to GitHub Issue, if one exists
Fixes https://github.com/spice2x/spice2x.github.io/issues/193

## Description of change
Merges guardianblue's changes plus some code review feedback.

Adds the ability to automatically type out the digits of a PIN when an
overlay shortcut is pressed.

## Compiling
💯 

## Testing
Tested:
* 32 bit popn
* TDJ, 1p/2p
* KFC, UFC
2025-03-23 18:05:38 -07:00
bicarus-dev 8241641502 audio: log errors when RegOpenKey fails for ASIO registry hooks (#273)
## Link to GitHub Issue, if one exists
n/a

## Description of change
When RegOpenKey* hooks fail when substituting user's ASIO driver, log a
warning message.

This can happen when the user types in the wrong value for `-iidxasio`
or `-sdvxasio`, which is a common issue.

## Compiling
🥇 

## Testing
Tested LDJ/KFC
2025-03-23 18:02:41 -07:00
bicarus-dev 6de122c9f8 api: allow lights.read() to be filtered by light names (#271)
## Link to GitHub Issue, if one exists
Fixes https://github.com/spice2x/spice2x.github.io/issues/179

## Description of change
Previously, lights.read() did not accept any parameters, instead it
returned all lights implemented by the current game.
Problem is that this is too large for some embedded implementations;
also just inefficient.

Add code to accept light names as strings and only return those in the
result.
For performance, use `robin_hood::unordered_map` to speed up the lookup
operation, which was previously a linear search.
Add this to Python wrapper library. Did not bother with others.

Also, as a bonus - update lights.write_reset() to also accept a flat
list of strings, as opposed to array containing a string, which was
silly.

## Compiling
 

## Testing
Tested using spiceremote and writing a short Python program.
2025-03-22 21:50:51 -07:00
bicarus-dev b267ad09ac Multiple "scenes" for screen resize (#270)
## Link to GitHub Issue, if one exists
Fixes #263 

## Description of change
Add "scenes" to screen resize.

* UI changes - F11 menu now has a "scene switcher" for `Scene 1/2/3/4`.
Clean up other UI bits.
* Add hotkeys for `Screen Resize Scene 1`, `Screen Resize 2`, and so on.
* Detect hot key and trigger resizes.
* Add additional objects to screen_resize.json for saving and loading
the new scene settings.
* Existing screen resize data is backwards compatible (can be
loaded/saved to JSON), and is treated as `Scene 1`.
* Add API endpoint for `Resize`, allow toggling resize on/off, switching
active scene
* Add Python wrapper. Did not bother with the other wrappers (Dart, C++,
etc)...

## Compiling
🥇 

## Testing
Still testing, but seems to work fine for TDJ.
2025-03-21 21:52:47 -07:00
sp2xdev e0dd371d61 Update README.md 2025-03-21 03:09:17 -07:00
bicarus-dev f8f45a0cbd When UCRT is in use, hackishly populate msvcrt!__argc and msvcrt!__argv for DLL hook compat (#267)
## Link to GitHub Issue, if one exists
#264 

## Description of change
When spice2x is compiled with UCRT, fix up `__argc` and `__argv` for
msvcrt since some DLL hooks rely on them.

## Compiling
👍 

## Testing
Tested on Win10 LTSC, Win11, and Win7.
2025-03-20 18:32:05 -07:00
bicarus-dev bf8ba050ce Improve render scale help text in F11 menu (#269)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Previously, F11 menu just provided text-based hint on how to enable
-windowscale option.

Instead, show a disabled checkbox that correctly reflects the current
state, nudging the user to read the tooltip.

Elaborate the option tooltip for -windowscale as well for consistency &
explain *why* you might want to enable this.

## Compiling
💯 

## Testing
Tested 32/64 bit.
2025-03-20 18:31:52 -07:00
109 changed files with 18597 additions and 8249 deletions
+32
View File
@@ -0,0 +1,32 @@
on: [push, pull_request]
name: Continuous Integration
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
fw-ci:
name: Build
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./src/spice2x
steps:
- uses: actions/checkout@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: |
calculatedSha=$(git rev-parse --short ${{ github.sha }})
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
- name: Compile
run: ./build_docker.sh
- uses: actions/upload-artifact@v4
with:
name: spice2x-ci-${{ env.COMMIT_SHORT_SHA }}
path: src/spice2x/bin
if-no-files-found: error
+13
View File
@@ -0,0 +1,13 @@
.vscode/*
# JetBrains IDEs
.idea
.idea_modules
*.iws
# CMake
cmake-build*
# user config
build_all.local.sh
build_docker.local.sh
+3
View File
@@ -55,3 +55,6 @@ We explicitly do **NOT** have a Discord server for dicussions - we try to do eve
Please see [CONTRIBUTING page](https://github.com/spice2x/spice2x.github.io/blob/main/CONTRIBUTING.md) for a full list of guidelines when submitting code.
## Additional information
Please read [README.md](https://github.com/spice2x/spice2x.github.io/blob/main/src/spice2x/README.md) inside `src/spice2x`.
+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/**
+3 -1
View File
@@ -16,4 +16,6 @@ build_docker.local.sh
bin/*
dist/*
external/cv2pdb/*
external/cv2pdb/*
.ccache/*
+23 -6
View File
@@ -211,14 +211,12 @@ add_compile_definitions(
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DACIOEMU_LOG")
# add project directory to include path so we can comfortably import
include_directories(${spicetools_SOURCE_DIR})
include_directories(${spicetools_SOURCE_DIR} ${spicetools_SOURCE_DIR}/external/imgui)
# add external libraries
add_subdirectory(external/fmt EXCLUDE_FROM_ALL)
add_subdirectory(external/discord-rpc EXCLUDE_FROM_ALL)
add_subdirectory(external/hash-library EXCLUDE_FROM_ALL)
add_compile_definitions(IMGUI_DISABLE_DEMO_WINDOWS)
add_compile_definitions(IMGUI_DISABLE_DEBUG_TOOLS)
add_subdirectory(external/imgui EXCLUDE_FROM_ALL)
add_subdirectory(external/minhook EXCLUDE_FROM_ALL)
add_subdirectory(external/cpu_features EXCLUDE_FROM_ALL)
@@ -302,6 +300,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
api/modules/drs.cpp
api/modules/lcd.cpp
api/modules/ddr.cpp
api/modules/resize.cpp
# avs
avs/core.cpp
@@ -441,6 +440,9 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/qks/qks.cpp
games/qks/io.cpp
games/qks/bi2x_hook.cpp
games/mfg/mfg.cpp
games/mfg/io.cpp
games/mfg/bi2a_hook.cpp
# hooks
hooks/audio/audio.cpp
@@ -512,7 +514,6 @@ set(SOURCE_FILES ${SOURCE_FILES}
overlay/overlay.cpp
overlay/window.cpp
overlay/imgui/extensions.cpp
overlay/imgui/impl_dx9.cpp
overlay/imgui/impl_spice.cpp
overlay/imgui/impl_sw.cpp
overlay/windows/acio_status_buffers.cpp
@@ -586,6 +587,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
util/sysutils.cpp
util/lz77.cpp
util/tapeled.cpp
util/execexe.cpp
)
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
@@ -605,6 +607,22 @@ 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 winscard winhttp
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
set_target_properties(spicetools_spice_laa PROPERTIES PREFIX "")
set_target_properties(spicetools_spice_laa PROPERTIES 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()
# spice64.exe
#############
@@ -624,7 +642,6 @@ IF(NOT MSVC)
set_target_properties(spicetools_spice64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# spicecfg.exe
##############
@@ -730,7 +747,7 @@ set_target_properties(spicetools_cfg
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_stubs_kbt spicetools_stubs_kld
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive32"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32"
+4 -5
View File
@@ -1,8 +1,7 @@
FROM spicetools/deps
WORKDIR /src
RUN chown user:user /src
USER user
COPY --chown=user:user --from=gitroot . /src/.git
COPY --chown=user:user . /src/src/spice2x
COPY --from=gitroot . /src/.git
COPY . /src/src/spice2x
WORKDIR /src/src/spice2x
CMD ["./build_all.sh"]
ENTRYPOINT ["./build_all.sh"]
+18
View File
@@ -181,6 +181,12 @@ All of those three modules have equally named methods for you to call.
- removes the override value from the objects specified by name
- if no names were passed, all overrides will be removed
##### Additional API for lights
- read(name: string, ...)
- same as read(), but you can specify light names
- write_reset(name: str, ...)
- same as write_reset(), but it accepts a flat list of strings
#### Touch
- read()
- returns an array of state objects containing id, x and y
@@ -246,6 +252,18 @@ which also means that your hex edits are applicable directly.
- info()
- returns information about the serial LCD controller some games use
#### Resize
- image_resize_enable(enable: bool)
- enables or disables image resize state
- image_resize_set_scene(scene: int)
- sets the active scene for image resize state; set to 0 to disable resize
## Native wrapper libraries
Spicetools provides wrapper libraries in: Arduino, C++, Dart, and Python.
Python is the only one that is fully spec compliant.
Other libraries may be missing features and contain bugs; please feel free to
contribute code to fill the gaps if you work on a project using these libraries.
## License
Unless otherwise noted, all files are licensed under the GPLv3.
See the LICENSE file for the full license text.
+20
View File
@@ -33,6 +33,11 @@ static int __cdecl ac_io_bmpu_control_1p_start_led_off() {
if (avs::game::is_model("KDM")) {
auto &lights = games::dea::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::P1Start), 0.f);
// MUSECA
} else if (avs::game::is_model("PIX")) {
auto &lights = games::museca::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::museca::Lights::Start), 0.f);
}
return 1;
@@ -44,6 +49,11 @@ static int __cdecl ac_io_bmpu_control_1p_start_led_on() {
if (avs::game::is_model("KDM")) {
auto &lights = games::dea::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::P1Start), 1.f);
// MUSECA
} else if (avs::game::is_model("PIX")) {
auto &lights = games::museca::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::museca::Lights::Start), 1.f);
}
return 1;
@@ -55,6 +65,11 @@ static int __cdecl ac_io_bmpu_control_2p_start_led_off() {
if (avs::game::is_model("KDM")) {
auto &lights = games::dea::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::P2Start), 0.f);
// MUSECA
} else if (avs::game::is_model("PIX")) {
auto &lights = games::museca::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::museca::Lights::Keypad), 0.f);
}
return 1;
@@ -66,6 +81,11 @@ static int __cdecl ac_io_bmpu_control_2p_start_led_on() {
if (avs::game::is_model("KDM")) {
auto &lights = games::dea::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::P2Start), 1.f);
// MUSECA
} else if (avs::game::is_model("PIX")) {
auto &lights = games::museca::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::museca::Lights::Keypad), 1.f);
}
return 1;
+3 -2
View File
@@ -4,8 +4,9 @@
#include "rawinput/rawinput.h"
#include "util/utils.h"
acioemu::ACIOHandle::ACIOHandle(LPCWSTR lpCOMPort) {
acioemu::ACIOHandle::ACIOHandle(LPCWSTR lpCOMPort, uint8_t iccaNodeCount) {
this->com_port = lpCOMPort;
this->icca_node_count = iccaNodeCount;
}
bool acioemu::ACIOHandle::open(LPCWSTR lpFileName) {
@@ -16,7 +17,7 @@ bool acioemu::ACIOHandle::open(LPCWSTR lpFileName) {
log_info("acioemu", "Opened {} (ACIO)", ws2s(com_port));
// ACIO device
acio_emu.add_device(new acioemu::ICCADevice(false, true, 2));
acio_emu.add_device(new acioemu::ICCADevice(false, true, icca_node_count));
return true;
}
+3 -1
View File
@@ -9,10 +9,12 @@ namespace acioemu {
private:
LPCWSTR com_port;
uint8_t icca_node_count;
acioemu::ACIOEmu acio_emu;
public:
ACIOHandle(LPCWSTR lpCOMPort);
ACIOHandle(LPCWSTR lpCOMPort, uint8_t iccaNodeCount = 2);
bool open(LPCWSTR lpFileName) override;
+3
View File
@@ -107,6 +107,8 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))
) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
} else if (avs::game::is_model({"VFG"})) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCB");
} else {
this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA");
}
@@ -333,6 +335,7 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
case ACIO_CMD_CLEAR:
case 0x30: // GetBoardProductNumber
case 0x31: // GetMicomInfo
case 0x3A: // ???
case 0x0116: // ???
case 0x0120: // ???
case 0xFF: // BROADCAST
+2
View File
@@ -27,6 +27,7 @@
#include "modules/lights.h"
#include "modules/memory.h"
#include "modules/touch.h"
#include "modules/resize.h"
#include "request.h"
#include "response.h"
@@ -403,6 +404,7 @@ void Controller::init_state(api::ClientState *state) {
state->modules.push_back(new modules::Lights());
state->modules.push_back(new modules::Memory());
state->modules.push_back(new modules::Touch());
state->modules.push_back(new modules::Resize());
}
void Controller::free_state(api::ClientState *state) {
+74 -46
View File
@@ -19,11 +19,16 @@ namespace api::modules {
functions["read"] = std::bind(&Lights::read, this, _1, _2);
functions["write"] = std::bind(&Lights::write, this, _1, _2);
functions["write_reset"] = std::bind(&Lights::write_reset, this, _1, _2);
lights = games::get_lights(eamuse_get_game());
this->lights = games::get_lights(eamuse_get_game());
for (auto &light : *this->lights) {
this->lights_by_names.emplace(light.getName(), light);
}
}
/**
* read()
* read(name: str, ...)
*/
void Lights::read(api::Request &req, Response &res) {
@@ -32,17 +37,39 @@ namespace api::modules {
return;
}
// add state for each light
for (auto &light : *this->lights) {
Value state(kArrayType);
Value light_name(light.getName().c_str(), res.doc()->GetAllocator());
Value light_state(GameAPI::Lights::readLight(RI_MGR, light));
Value light_enabled(light.override_enabled);
state.PushBack(light_name, res.doc()->GetAllocator());
state.PushBack(light_state, res.doc()->GetAllocator());
state.PushBack(light_enabled, res.doc()->GetAllocator());
res.add_data(state);
// all lights for this game
if (req.params.Size() == 0) {
// add state for each light
for (auto &light : *this->lights) {
get_light(light, res);
}
return;
}
// 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 (this->lights_by_names.contains(name)) {
get_light(this->lights_by_names.at(name).get(), res);
}
}
}
void Lights::get_light(Light &light, Response &res) {
Value state(kArrayType);
Value light_name(light.getName().c_str(), res.doc()->GetAllocator());
Value light_state(GameAPI::Lights::readLight(RI_MGR, light));
Value light_enabled(light.override_enabled);
state.PushBack(light_name, res.doc()->GetAllocator());
state.PushBack(light_state, res.doc()->GetAllocator());
state.PushBack(light_enabled, res.doc()->GetAllocator());
res.add_data(state);
}
/**
@@ -90,6 +117,7 @@ namespace api::modules {
/**
* write_reset()
* write_reset(name: str, ...)
* write_reset([name: str], ...)
*/
void Lights::write_reset(Request &req, Response &res) {
@@ -119,26 +147,28 @@ namespace api::modules {
// loop parameters
for (Value &param : req.params.GetArray()) {
const char* light_name = nullptr;
// check params
if (!param.IsArray()) {
error(res, "parameters must be arrays");
return;
if (param.IsArray()) {
if (param.Size() < 1) {
error_params_insufficient(res);
continue;
}
if (!param[0].IsString()) {
error_type(res, "name", "string");
continue;
}
// get params
light_name = param[0].GetString();
} else if (param.IsString()) {
light_name = param.GetString();
} else {
error(res, "parameters must be arrays or strings");
}
if (param.Size() < 1) {
error_params_insufficient(res);
continue;
}
if (!param[0].IsString()) {
error_type(res, "name", "string");
continue;
}
// get params
auto light_name = param[0].GetString();
// write analog state
if (!this->write_light_reset(light_name)) {
if (light_name && !this->write_light_reset(light_name)) {
error_unknown(res, "analog", light_name);
continue;
}
@@ -153,21 +183,20 @@ namespace api::modules {
}
// find light
for (auto &light : *this->lights) {
if (light.getName() == name) {
light.override_state = CLAMP(state, 0.f, 1.f);
light.override_enabled = true;
if (this->lights_by_names.contains(name)) {
auto &light = this->lights_by_names.at(name).get();
light.override_state = CLAMP(state, 0.f, 1.f);
light.override_enabled = true;
if (cfg::CONFIGURATOR_STANDALONE) {
GameAPI::Lights::writeLight(RI_MGR, light, state);
}
return true;
if (cfg::CONFIGURATOR_STANDALONE) {
GameAPI::Lights::writeLight(RI_MGR, light, state);
}
}
// unknown light
return false;
return true;
} else {
// unknown light
return false;
}
}
bool Lights::write_light_reset(std::string name) {
@@ -178,14 +207,13 @@ namespace api::modules {
}
// find light
for (auto &light : *this->lights) {
if (light.getName() == name) {
light.override_enabled = false;
return true;
}
if (this->lights_by_names.contains(name)) {
auto &light = this->lights_by_names.at(name).get();
light.override_enabled = false;
return true;
} else {
// unknown light
return false;
}
// unknown light
return false;
}
}
+4
View File
@@ -1,6 +1,8 @@
#pragma once
#include <vector>
#include <external/robin_hood.h>
#include "api/module.h"
#include "api/request.h"
#include "cfg/api.h"
@@ -15,6 +17,7 @@ namespace api::modules {
// state
std::vector<Light> *lights;
robin_hood::unordered_map<std::string, std::reference_wrapper<Light>> lights_by_names;
// function definitions
void read(Request &req, Response &res);
@@ -22,6 +25,7 @@ namespace api::modules {
void write_reset(Request &req, Response &res);
// helper
void get_light(Light &light, Response &res);
bool write_light(std::string name, float state);
bool write_light_reset(std::string name);
};
+53
View File
@@ -0,0 +1,53 @@
#include "resize.h"
#include "external/rapidjson/document.h"
#include "cfg/screen_resize.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
static thread_local std::vector<uint8_t> CAPTURE_BUFFER;
Resize::Resize() : Module("resize") {
functions["image_resize_enable"] = std::bind(&Resize::image_resize_enable, this, _1, _2);
functions["image_resize_set_scene"] = std::bind(&Resize::image_resize_set_scene, this, _1, _2);
}
/**
* image_resize_enable(enable: bool)
*/
void Resize::image_resize_enable(Request &req, Response &res) {
if (req.params.Size() < 1) {
return error_params_insufficient(res);
}
if (!req.params[0].IsBool()) {
return error_type(res, "enable", "bool");
}
cfg::SCREENRESIZE->enable_screen_resize = req.params[0].GetBool();
}
/**
* image_resize_set_scene(scene: int)
*/
void Resize::image_resize_set_scene(Request &req, Response &res) {
if (req.params.Size() < 1) {
return error_params_insufficient(res);
}
if (!req.params[0].IsInt()) {
return error_type(res, "scene", "int");
}
const auto scene = req.params[0].GetInt();
if (scene < 0 || (int)std::size(cfg::SCREENRESIZE->scene_settings) < scene) {
return error(res, "invalid scene number");
}
if (scene == 0) {
cfg::SCREENRESIZE->enable_screen_resize = false;
} else {
cfg::SCREENRESIZE->enable_screen_resize = true;
cfg::SCREENRESIZE->screen_resize_current_scene = scene - 1;
}
}
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class Resize : public Module {
public:
Resize();
private:
// function definitions
void image_resize_enable(Request &req, Response &res);
void image_resize_set_scene(Request &req, Response &res);
};
}
@@ -12,3 +12,4 @@ from .keypads import *
from .lights import *
from .memory import *
from .touch import *
from .resize import *
@@ -2,8 +2,14 @@ from .connection import Connection
from .request import Request
def lights_read(con: Connection):
res = con.request(Request("lights", "read"))
def lights_read(con: Connection, light_names=None):
req = Request("lights", "read")
if light_names:
for light_name in light_names:
req.add_param(light_name)
res = con.request(req)
return res.get_data()
@@ -0,0 +1,12 @@
from .connection import Connection
from .request import Request
def image_resize_enable(con: Connection, enable: bool):
req = Request("resize", "image_resize_enable")
req.add_param(enable)
con.request(req)
def image_resize_set_scene(con: Connection, scene: int):
req = Request("resize", "image_resize_set_scene")
req.add_param(scene)
con.request(req)
@@ -397,6 +397,65 @@ class LightsTab(ttk.Frame):
# set text
self.txt_lights.set_text(txt)
class ResizeTab(ttk.Frame):
"""Resize tab."""
def __init__(self, app, parent, **kwargs):
# init frame
ttk.Frame.__init__(self, parent, **kwargs)
self.app = app
self.parent = parent
# scale grid
self.columnconfigure(0, weight=1)
# image resize
self.resize = ttk.Frame(self, padding=(8, 8, 8, 8))
self.resize.grid(row=0, column=0, sticky=tk.E+tk.W)
self.resize.columnconfigure(0, weight=1)
self.resize.columnconfigure(1, weight=1)
self.resize_lbl = ttk.Label(self.resize, text="Image Resize")
self.resize_lbl.grid(row=0, columnspan=2)
self.resize_off = ttk.Button(self.resize, text="Disable", command=self.action_resize_false)
self.resize_off.grid(row=2, column=0, sticky=NSEW, padx=2, pady=2)
self.resize_on = ttk.Button(self.resize, text="Enable", command=self.action_resize_on)
self.resize_on.grid(row=2, column=1, sticky=NSEW, padx=2, pady=2)
self.resize_scene_1 = ttk.Button(self.resize, text="Scene 1", command=self.action_resize_scene_1)
self.resize_scene_1.grid(row=3, column=0, sticky=NSEW, padx=2, pady=2)
self.resize_scene_2 = ttk.Button(self.resize, text="Scene 2", command=self.action_resize_scene_2)
self.resize_scene_2.grid(row=3, column=1, sticky=NSEW, padx=2, pady=2)
self.resize_scene_3 = ttk.Button(self.resize, text="Scene 3", command=self.action_resize_scene_3)
self.resize_scene_3.grid(row=4, column=0, sticky=NSEW, padx=2, pady=2)
self.resize_scene_4 = ttk.Button(self.resize, text="Scene 4", command=self.action_resize_scene_4)
self.resize_scene_4.grid(row=4, column=1, sticky=NSEW, padx=2, pady=2)
@api_action
def action_resize_on(self):
spiceapi.image_resize_enable(self.app.connection, True)
@api_action
def action_resize_false(self):
spiceapi.image_resize_enable(self.app.connection, False)
@api_action
def action_resize_scene_1(self):
spiceapi.image_resize_set_scene(self.app.connection, 1)
@api_action
def action_resize_scene_2(self):
spiceapi.image_resize_set_scene(self.app.connection, 2)
@api_action
def action_resize_scene_3(self):
spiceapi.image_resize_set_scene(self.app.connection, 3)
@api_action
def action_resize_scene_4(self):
spiceapi.image_resize_set_scene(self.app.connection, 4)
class MainApp(ttk.Frame):
"""The main application frame."""
@@ -419,6 +478,8 @@ class MainApp(ttk.Frame):
self.tabs.add(self.tab_analogs, text="Analogs")
self.tab_lights = LightsTab(self, self.tabs)
self.tabs.add(self.tab_lights, text="Lights")
self.tab_resize = ResizeTab(self, self.tabs)
self.tabs.add(self.tab_resize, text="Resize")
self.tab_manual = ManualTab(self, self.tabs)
self.tabs.add(self.tab_manual, text="Manual")
self.tabs.pack(expand=True, fill=tk.BOTH)
+11 -6
View File
@@ -76,18 +76,23 @@ 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"
"-------------------------------------------------------------------\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());
}
if (!fileutils::file_exists(dll_path)) {
Regular → Executable
+58 -26
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")
@@ -41,7 +60,7 @@ DIST_ENABLE=1
DIST_FOLDER="./dist"
DIST_NAME="spice2x-$(date +%y)-$(date +%m)-$(date +%d).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_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_spice spicetools_spice_laa"
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64"
# determine build type
@@ -57,7 +76,7 @@ then
fi
# determine number of cores
CORES=$(awk '/^processor\t/ {cores[$NF]++} END {print length(cores)}' /proc/cpuinfo)
CORES=$(nproc)
# print information
echo ""
@@ -74,34 +93,45 @@ echo "Build Type: $BUILD_TYPE"
echo "Cores: $CORES"
echo ""
# 32 bit
echo "Building 32bit targets..."
echo "========================="
if ((CLEAN_BUILD > 0))
if ((IGNORE_CACHE > 0))
then
rm -rf ${BUILDDIR_32}
echo "Ignoring build cache..."
else
export CCACHE_DIR="$(pwd)/.ccache"
export CMAKE_CXX_COMPILER_LAUNCHER=ccache
export CMAKE_C_COMPILER_LAUNCHER=ccache
fi
mkdir -p ${BUILDDIR_32}
pushd ${BUILDDIR_32} > /dev/null
cmake -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} $OLDPWD && make -j ${CORES} ${TARGETS_32}
popd > /dev/null
# 64 bit
echo ""
echo "Building 64bit targets..."
echo "========================="
if ((CLEAN_BUILD > 0))
then
rm -rf ${BUILDDIR_64}
fi
mkdir -p ${BUILDDIR_64}
pushd ${BUILDDIR_64} > /dev/null
cmake -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} $OLDPWD && make -j ${CORES} ${TARGETS_64}
popd > /dev/null
time (
# 32 bit
echo "Building 32bit targets..."
echo "========================="
if ((CLEAN_BUILD > 0))
then
rm -rf ${BUILDDIR_32}
fi
mkdir -p ${BUILDDIR_32}
pushd ${BUILDDIR_32} > /dev/null
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} $OLDPWD && ninja ${TARGETS_32}
popd > /dev/null
echo ""
echo "Compilation process done :)"
echo "==========================="
# 64 bit
echo ""
echo "Building 64bit targets..."
echo "========================="
if ((CLEAN_BUILD > 0))
then
rm -rf ${BUILDDIR_64}
fi
mkdir -p ${BUILDDIR_64}
pushd ${BUILDDIR_64} > /dev/null
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} $OLDPWD && ninja ${TARGETS_64}
popd > /dev/null
echo ""
echo "Compilation process done :)"
echo "==========================="
)
# generate PDBs
if false # ((DEBUG > 0))
@@ -153,6 +183,7 @@ rm -rf ${OUTDIR}
mkdir -p ${OUTDIR}
#mkdir -p ${OUTDIR}/stubs/32
mkdir -p ${OUTDIR}/stubs/64
mkdir -p ${OUTDIR}/extras/largeaddressaware
if false # ((DEBUG > 0))
then
# debug files
@@ -173,6 +204,7 @@ else
# release files
cp ${BUILDDIR_32}/spicetools/spicecfg.exe ${OUTDIR} 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/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
+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
Regular → Executable
+2 -4
View File
@@ -1,6 +1,4 @@
#!/bin/bash
export DOCKER_BUILDKIT=0
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 -it -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 "$@"
+2
View File
@@ -0,0 +1,2 @@
del /s /q .ccache
call build_docker.bat
+29 -24
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");
@@ -76,14 +76,18 @@ namespace cfg {
eamuse_get_game(),
use_game_setting,
root);
load_int_value(doc, root + "offset_x", this->offset_x);
load_int_value(doc, root + "offset_y", this->offset_y);
load_float_value(doc, root + "scale_x", this->scale_x);
load_float_value(doc, root + "scale_y", this->scale_y);
load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize);
load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter);
load_bool_value(doc, root + "keep_aspect_ratio", this->keep_aspect_ratio);
load_bool_value(doc, root + "centered", this->centered);
for (size_t i = 0; i < std::size(this->scene_settings); i++) {
auto& scene = this->scene_settings[i];
const std::string prefix = fmt::format("scenes/{}/", i);
load_int_value(doc, root + prefix + "offset_x", scene.offset_x);
load_int_value(doc, root + prefix + "offset_y", scene.offset_y);
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);
}
// windowed settings are always under game settings
root = "/sp2x_games/" + eamuse_get_game() + "/";
@@ -166,8 +170,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()) {
@@ -189,14 +191,17 @@ namespace cfg {
root);
// full screen image settings
rapidjson::Pointer(root + "offset_x").Set(doc, this->offset_x);
rapidjson::Pointer(root + "offset_y").Set(doc, this->offset_y);
rapidjson::Pointer(root + "scale_x").Set(doc, this->scale_x);
rapidjson::Pointer(root + "scale_y").Set(doc, this->scale_y);
rapidjson::Pointer(root + "enable_screen_resize").Set(doc, this->enable_screen_resize);
rapidjson::Pointer(root + "enable_linear_filter").Set(doc, this->enable_linear_filter);
rapidjson::Pointer(root + "keep_aspect_ratio").Set(doc, this->keep_aspect_ratio);
rapidjson::Pointer(root + "centered").Set(doc, this->centered);
for (size_t i = 0; i < std::size(this->scene_settings); i++) {
auto& scene = this->scene_settings[i];
const std::string prefix = fmt::format("scenes/{}/", i);
rapidjson::Pointer(root + prefix + "offset_x").Set(doc, scene.offset_x);
rapidjson::Pointer(root + prefix + "offset_y").Set(doc, scene.offset_y);
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);
}
// windowed mode settings
rapidjson::Pointer(root + "w_always_on_top").Set(doc, this->window_always_on_top);
@@ -214,10 +219,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");
}
}
}
+10 -6
View File
@@ -14,6 +14,14 @@ namespace cfg {
ResizableFrame = 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;
};
extern std::optional<std::string> SCREEN_RESIZE_CFG_PATH_OVERRIDE;
class ScreenResize {
@@ -31,14 +39,10 @@ namespace cfg {
~ScreenResize();
// full screen (directx) image settings
int offset_x = 0;
int offset_y = 0;
float scale_x = 1.0;
float scale_y = 1.0;
bool enable_screen_resize = false;
int8_t screen_resize_current_scene = 0;
bool enable_linear_filter = true;
bool keep_aspect_ratio = true;
bool centered = true;
fullscreen_setting scene_settings[4];
// windowed mode sizing
// Windows terminology:
+25
View File
@@ -1,3 +1,28 @@
04/25/2025 [spice2x]
LargeAddressAware variant of spice.exe for Metal Gear
Save new JSON config files to %appdata%/spice2x
Various bug fixes
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
+1 -1
View File
@@ -1,4 +1,4 @@
cmake_minimum_required (VERSION 3.2.0)
cmake_minimum_required (VERSION 3.12)
project (DiscordRPC)
include(GNUInstallDirs)
+6 -14
View File
@@ -1,25 +1,17 @@
FROM archlinux:base
FROM ghcr.io/archlinux/archlinux:base-devel
RUN pacman --noconfirm -Syu gettext \
base-devel \
git \
RUN pacman --noconfirm -Syu git \
bash \
zip \
upx \
sudo \
binutils \
file \
make \
gcc \
fakeroot \
diffutils \
ninja \
cmake \
awk \
unzip \
mingw-w64-crt \
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.git --depth=1 && cd yay && makepkg --noconfirm -si && yay --noconfirm -S mingw-w64-cmake"
RUN su user -c "cd /tmp/ && git clone https://aur.archlinux.org/yay-bin.git --depth=1 && cd yay-bin && makepkg --noconfirm -si && yay --noconfirm -S mingw-w64-cmake"
+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
+5 -3
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.9)
cmake_minimum_required(VERSION 3.12)
project(imgui CXX)
# dear imgui uses the C++ virtual interfaces for DirectX
@@ -8,7 +8,8 @@ set(IMGUI_HEADERS
imconfig.h
imgui.h
imgui_internal.h
imgui_stdlib.h
backends/imgui_impl_dx9.h
misc/cpp/imgui_stdlib.h
)
set(IMGUI_SOURCES
imgui.cpp
@@ -16,7 +17,8 @@ set(IMGUI_SOURCES
imgui_tables.cpp
imgui_widgets.cpp
imgui_demo.cpp
imgui_stdlib.cpp
backends/imgui_impl_dx9.cpp
misc/cpp/imgui_stdlib.cpp
)
add_library(imgui STATIC ${IMGUI_HEADERS} ${IMGUI_SOURCES})
@@ -22,14 +22,14 @@
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX9_RenderDrawData() in the .h file so you can call it yourself.
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
#include "impl_dx9.h"
#include "imgui_impl_dx9.h"
#include <algorithm>
// DirectX
#include <d3d9.h>
#include "external/imgui/imgui.h"
#include "imgui.h"
// allow std::min use
#ifdef min
@@ -327,7 +327,7 @@ void ImGui_ImplDX9_InvalidateDeviceObjects() {
if (g_FontTexture) {
g_FontTexture->Release();
g_FontTexture = NULL;
ImGui::GetIO().Fonts->TexID = NULL;
ImGui::GetIO().Fonts->TexID = 0;
} // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well.
}
@@ -11,7 +11,7 @@
#pragma once
#include "external/imgui/imgui.h"
#include "imgui.h"
struct IDirect3DDevice9;
+33 -16
View File
@@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// COMPILE-TIME OPTIONS FOR DEAR IMGUI
// DEAR IMGUI COMPILE-TIME OPTIONS
// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure.
// You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions.
//-----------------------------------------------------------------------------
@@ -9,7 +9,7 @@
// You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp
// files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures.
// Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts.
// Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using.
// Call IMGUI_CHECKVERSION() from your .cpp file to verify that the data structures your files are using are matching the ones imgui.cpp is using.
//-----------------------------------------------------------------------------
#pragma once
@@ -21,27 +21,29 @@
//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows
// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility.
// DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions()
// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details.
//#define IMGUI_API __declspec( dllexport )
//#define IMGUI_API __declspec( dllimport )
// - Windows DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions()
// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details.
//#define IMGUI_API __declspec(dllexport) // MSVC Windows: DLL export
//#define IMGUI_API __declspec(dllimport) // MSVC Windows: DLL import
//#define IMGUI_API __attribute__((visibility("default"))) // GCC/Clang: override visibility when set is hidden
//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names.
//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names.
//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87: disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This will be folded into IMGUI_DISABLE_OBSOLETE_FUNCTIONS in a few versions.
//#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87+ disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This is automatically done by IMGUI_DISABLE_OBSOLETE_FUNCTIONS.
//---- Disable all of Dear ImGui or don't implement standard windows/tools.
// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.
//#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty.
//#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty.
//#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowStackToolWindow() will be empty (this was called IMGUI_DISABLE_METRICS_WINDOW before 1.88).
#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty.
//#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowIDStackToolWindow() will be empty.
//---- Don't implement some functions to reduce linkage requirements.
//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a)
#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW)
//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a)
//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, ime).
//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME).
//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
//#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")).
//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf)
//#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself.
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
@@ -49,8 +51,13 @@
#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
//---- Enable Test Engine / Automation features.
//#define IMGUI_ENABLE_TEST_ENGINE // Enable imgui_test_engine hooks. Generally set automatically by include "imgui_te_config.h", see Test Engine for details.
//---- Include imgui_user.h at the end of imgui.h as a convenience
// May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included.
//#define IMGUI_INCLUDE_IMGUI_USER_H
//#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h"
//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another)
#define IMGUI_USE_BGRA_PACKED_COLOR
@@ -58,16 +65,17 @@
//---- Performance optimization for DX9
#define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; }
//---- Use 32-bit for ImWchar (default is 16-bit) to support unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
//#define IMGUI_USE_WCHAR32
//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version
// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files.
//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h"
//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h"
//#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if enabled
//#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if IMGUI_USE_STB_SPRINTF is defined.
//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION
//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION
//#define IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION // only disabled if IMGUI_USE_STB_SPRINTF is defined.
//---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined)
// Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h.
@@ -78,6 +86,15 @@
// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.
//#define IMGUI_ENABLE_FREETYPE
//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT)
// Only works in combination with IMGUI_ENABLE_FREETYPE.
// - lunasvg is currently easier to acquire/install, as e.g. it is part of vcpkg.
// - plutosvg will support more fonts and may load them faster. It currently requires to be built manually but it is fairly easy. See misc/freetype/README for instructions.
// - Both require headers to be available in the include path + program to be linked with the library code (not provided).
// - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement)
//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG
//#define IMGUI_ENABLE_FREETYPE_LUNASVG
//---- Use stb_truetype to build and rasterize the font atlas (default)
// The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend.
//#define IMGUI_ENABLE_STB_TRUETYPE
@@ -108,7 +125,7 @@
//typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data);
//#define ImDrawCallback MyImDrawCallback
//---- Debug Tools: Macro to break in Debugger
//---- Debug Tools: Macro to break in Debugger (we provide a default implementation of this in the codebase)
// (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.)
//#define IM_DEBUG_BREAK IM_ASSERT(0)
//#define IM_DEBUG_BREAK __debugbreak()
@@ -116,10 +133,10 @@
//---- Debug Tools: Enable slower asserts
//#define IMGUI_DEBUG_PARANOID
//---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files.
//---- Tip: You can add extra functions within the ImGui:: namespace from anywhere (e.g. your own sources/header files)
/*
namespace ImGui
{
void MyFunction(const char* name, const MyMatrix44& v);
void MyFunction(const char* name, MyMatrix44* mtx);
}
*/
+4651 -2301
View File
File diff suppressed because it is too large Load Diff
+1497 -902
View File
File diff suppressed because it is too large Load Diff
+3015 -663
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+139 -107
View File
@@ -2,8 +2,11 @@
// This is a slightly modified version of stb_textedit.h 1.14.
// Those changes would need to be pushed into nothings/stb:
// - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321)
// - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000)
// - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783)
// - Added name to struct or it may be forward declared in our code.
// - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925)
// Grep for [DEAR IMGUI] to find the changes.
// - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_*
// stb_textedit.h - v1.14 - public domain - Sean Barrett
// Development of this library was sponsored by RAD Game Tools
@@ -30,7 +33,7 @@
// DEPENDENCIES
//
// Uses the C runtime function 'memmove', which you can override
// by defining STB_TEXTEDIT_memmove before the implementation.
// by defining IMSTB_TEXTEDIT_memmove before the implementation.
// Uses no other functions. Performs no runtime allocations.
//
//
@@ -40,7 +43,7 @@
// 1.13 (2019-02-07) fix bug in undo size management
// 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash
// 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield
// 1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual
// 1.10 (2016-10-25) suppress warnings about casting away const with -Wcast-qual
// 1.9 (2016-08-27) customizable move-by-word
// 1.8 (2016-04-02) better keyboard handling when mouse button is down
// 1.7 (2015-09-13) change y range handling in case baseline is non-0
@@ -208,6 +211,7 @@
// int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
// int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)
// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key)
// void stb_textedit_text(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int text_len)
//
// Each of these functions potentially updates the string and updates the
// state.
@@ -242,7 +246,12 @@
// various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit
// set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is
// clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to
// anything other type you wante before including.
// anything other type you want before including.
// if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are
// transformed into text and stb_textedit_text() is automatically called.
//
// text: [DEAR IMGUI] added 2024-09
// call this to text inputs sent to the textfield.
//
//
// When rendering, you can read the cursor position and selection state from
@@ -274,8 +283,8 @@
////
////
#ifndef INCLUDE_STB_TEXTEDIT_H
#define INCLUDE_STB_TEXTEDIT_H
#ifndef INCLUDE_IMSTB_TEXTEDIT_H
#define INCLUDE_IMSTB_TEXTEDIT_H
////////////////////////////////////////////////////////////////////////
//
@@ -286,38 +295,38 @@
// and undo state.
//
#ifndef STB_TEXTEDIT_UNDOSTATECOUNT
#define STB_TEXTEDIT_UNDOSTATECOUNT 99
#ifndef IMSTB_TEXTEDIT_UNDOSTATECOUNT
#define IMSTB_TEXTEDIT_UNDOSTATECOUNT 99
#endif
#ifndef STB_TEXTEDIT_UNDOCHARCOUNT
#define STB_TEXTEDIT_UNDOCHARCOUNT 999
#ifndef IMSTB_TEXTEDIT_UNDOCHARCOUNT
#define IMSTB_TEXTEDIT_UNDOCHARCOUNT 999
#endif
#ifndef STB_TEXTEDIT_CHARTYPE
#define STB_TEXTEDIT_CHARTYPE int
#ifndef IMSTB_TEXTEDIT_CHARTYPE
#define IMSTB_TEXTEDIT_CHARTYPE int
#endif
#ifndef STB_TEXTEDIT_POSITIONTYPE
#define STB_TEXTEDIT_POSITIONTYPE int
#ifndef IMSTB_TEXTEDIT_POSITIONTYPE
#define IMSTB_TEXTEDIT_POSITIONTYPE int
#endif
typedef struct
{
// private data
STB_TEXTEDIT_POSITIONTYPE where;
STB_TEXTEDIT_POSITIONTYPE insert_length;
STB_TEXTEDIT_POSITIONTYPE delete_length;
IMSTB_TEXTEDIT_POSITIONTYPE where;
IMSTB_TEXTEDIT_POSITIONTYPE insert_length;
IMSTB_TEXTEDIT_POSITIONTYPE delete_length;
int char_storage;
} StbUndoRecord;
typedef struct
{
// private data
StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT];
STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT];
StbUndoRecord undo_rec [IMSTB_TEXTEDIT_UNDOSTATECOUNT];
IMSTB_TEXTEDIT_CHARTYPE undo_char[IMSTB_TEXTEDIT_UNDOCHARCOUNT];
short undo_point, redo_point;
int undo_char_point, redo_char_point;
} StbUndoState;
typedef struct
typedef struct STB_TexteditState
{
/////////////////////
//
@@ -371,7 +380,7 @@ typedef struct
float ymin,ymax; // height of row above and below baseline
int num_chars;
} StbTexteditRow;
#endif //INCLUDE_STB_TEXTEDIT_H
#endif //INCLUDE_IMSTB_TEXTEDIT_H
////////////////////////////////////////////////////////////////////////////
@@ -384,11 +393,11 @@ typedef struct
// implementation isn't include-guarded, since it might have indirectly
// included just the "header" portion
#ifdef STB_TEXTEDIT_IMPLEMENTATION
#ifdef IMSTB_TEXTEDIT_IMPLEMENTATION
#ifndef STB_TEXTEDIT_memmove
#ifndef IMSTB_TEXTEDIT_memmove
#include <string.h>
#define STB_TEXTEDIT_memmove memmove
#define IMSTB_TEXTEDIT_memmove memmove
#endif
@@ -398,7 +407,7 @@ typedef struct
//
// traverse the layout to locate the nearest character to a display position
static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y)
static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
{
StbTexteditRow r;
int n = STB_TEXTEDIT_STRINGLEN(str);
@@ -437,13 +446,13 @@ static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y)
if (x < r.x1) {
// search characters in row for one that straddles 'x'
prev_x = r.x0;
for (k=0; k < r.num_chars; ++k) {
for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) {
float w = STB_TEXTEDIT_GETWIDTH(str, i, k);
if (x < prev_x+w) {
if (x < prev_x+w/2)
return k+i;
else
return k+i+1;
return IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k);
}
prev_x += w;
}
@@ -458,7 +467,7 @@ static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y)
}
// API click: on mouse down, move the cursor to the clicked location, and reset the selection
static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
{
// In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
// goes off the top or bottom of the text
@@ -476,7 +485,7 @@ static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *stat
}
// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location
static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
{
int p = 0;
@@ -502,11 +511,11 @@ static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state
//
// forward declarations
static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state);
static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state);
static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length);
static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state);
static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state);
static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length);
static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length);
static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length);
static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length);
typedef struct
{
@@ -518,7 +527,7 @@ typedef struct
// find the x/y location of a character, and remember info about the previous row in
// case we get a move-up event (for page up, we'll have to rescan)
static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line)
static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING *str, int n, int single_line)
{
StbTexteditRow r;
int prev_start = 0;
@@ -549,7 +558,10 @@ static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *s
i += r.num_chars;
find->y += r.baseline_y_delta;
if (i == z) // [DEAR IMGUI]
{
r.num_chars = 0; // [DEAR IMGUI]
break; // [DEAR IMGUI]
}
}
find->first_char = first = i;
@@ -559,14 +571,14 @@ static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *s
// now scan to find xpos
find->x = r.x0;
for (i=0; first+i < n; ++i)
for (i=0; first+i < n; i = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, first + i) - first)
find->x += STB_TEXTEDIT_GETWIDTH(str, first, i);
}
#define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end)
// make the selection/cursor state valid if client altered the string
static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
static void stb_textedit_clamp(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
{
int n = STB_TEXTEDIT_STRINGLEN(str);
if (STB_TEXT_HAS_SELECTION(state)) {
@@ -580,7 +592,7 @@ static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *stat
}
// delete characters while updating undo
static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len)
static void stb_textedit_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len)
{
stb_text_makeundo_delete(str, state, where, len);
STB_TEXTEDIT_DELETECHARS(str, where, len);
@@ -588,7 +600,7 @@ static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *sta
}
// delete the section
static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
static void stb_textedit_delete_selection(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
{
stb_textedit_clamp(str, state);
if (STB_TEXT_HAS_SELECTION(state)) {
@@ -625,7 +637,7 @@ static void stb_textedit_move_to_first(STB_TexteditState *state)
}
// move cursor to last character of selection
static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
{
if (STB_TEXT_HAS_SELECTION(state)) {
stb_textedit_sortselection(state);
@@ -636,14 +648,25 @@ static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditStat
}
}
// [DEAR IMGUI]
// Functions must be implemented for UTF8 support
// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.
// There is not necessarily a '[DEAR IMGUI]' at the usage sites.
#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx) (idx - 1)
#endif
#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx) (idx + 1)
#endif
#ifdef STB_TEXTEDIT_IS_SPACE
static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx )
static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )
{
return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1;
}
#ifndef STB_TEXTEDIT_MOVEWORDLEFT
static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c )
static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c )
{
--c; // always move at least one character
while( c >= 0 && !is_word_boundary( str, c ) )
@@ -658,7 +681,7 @@ static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c )
#endif
#ifndef STB_TEXTEDIT_MOVEWORDRIGHT
static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c )
static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c )
{
const int len = STB_TEXTEDIT_STRINGLEN(str);
++c; // always move at least one character
@@ -685,7 +708,7 @@ static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state)
}
// API cut: delete selection
static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
static int stb_textedit_cut(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
{
if (STB_TEXT_HAS_SELECTION(state)) {
stb_textedit_delete_selection(str,state); // implicitly clamps
@@ -696,7 +719,7 @@ static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
}
// API paste: replace existing selection with passed-in text
static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)
static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE *text, int len)
{
// if there's a selection, the paste should delete it
stb_textedit_clamp(str, state);
@@ -716,36 +739,44 @@ static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditSta
#define STB_TEXTEDIT_KEYTYPE int
#endif
// [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility.
static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
{
// can't add newline in single-line mode
if (text[0] == '\n' && state->single_line)
return;
if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {
state->cursor += text_len;
state->has_preferred_x = 0;
}
}
else {
stb_textedit_delete_selection(str, state); // implicitly clamps
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {
stb_text_makeundo_insert(state, state->cursor, text_len);
state->cursor += text_len;
state->has_preferred_x = 0;
}
}
}
// API key: process a keyboard input
static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key)
static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key)
{
retry:
switch (key) {
default: {
#ifdef STB_TEXTEDIT_KEYTOTEXT
int c = STB_TEXTEDIT_KEYTOTEXT(key);
if (c > 0) {
STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c;
// can't add newline in single-line mode
if (c == '\n' && state->single_line)
break;
if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) {
++state->cursor;
state->has_preferred_x = 0;
}
} else {
stb_textedit_delete_selection(str,state); // implicitly clamps
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) {
stb_text_makeundo_insert(state, state->cursor, 1);
++state->cursor;
state->has_preferred_x = 0;
}
}
IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c;
stb_textedit_text(str, state, &ch, 1);
}
#endif
break;
}
@@ -771,7 +802,7 @@ retry:
stb_textedit_move_to_first(state);
else
if (state->cursor > 0)
--state->cursor;
state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);
state->has_preferred_x = 0;
break;
@@ -780,7 +811,7 @@ retry:
if (STB_TEXT_HAS_SELECTION(state))
stb_textedit_move_to_last(str, state);
else
++state->cursor;
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
stb_textedit_clamp(str, state);
state->has_preferred_x = 0;
break;
@@ -790,7 +821,7 @@ retry:
stb_textedit_prep_selection_at_cursor(state);
// move selection left
if (state->select_end > 0)
--state->select_end;
state->select_end = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->select_end);
state->cursor = state->select_end;
state->has_preferred_x = 0;
break;
@@ -840,7 +871,7 @@ retry:
case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT:
stb_textedit_prep_selection_at_cursor(state);
// move selection right
++state->select_end;
state->select_end = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->select_end);
stb_textedit_clamp(str, state);
state->cursor = state->select_end;
state->has_preferred_x = 0;
@@ -889,14 +920,14 @@ retry:
x = row.x0;
for (i=0; i < row.num_chars; ++i) {
float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);
#ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE
if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE)
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
break;
#endif
x += dx;
if (x > goal_x)
break;
++state->cursor;
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
}
stb_textedit_clamp(str, state);
@@ -951,14 +982,14 @@ retry:
x = row.x0;
for (i=0; i < row.num_chars; ++i) {
float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);
#ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE
if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE)
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
break;
#endif
x += dx;
if (x > goal_x)
break;
++state->cursor;
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
}
stb_textedit_clamp(str, state);
@@ -986,7 +1017,7 @@ retry:
else {
int n = STB_TEXTEDIT_STRINGLEN(str);
if (state->cursor < n)
stb_textedit_delete(str, state, state->cursor, 1);
stb_textedit_delete(str, state, state->cursor, IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor) - state->cursor);
}
state->has_preferred_x = 0;
break;
@@ -998,8 +1029,9 @@ retry:
else {
stb_textedit_clamp(str, state);
if (state->cursor > 0) {
stb_textedit_delete(str, state, state->cursor-1, 1);
--state->cursor;
int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);
stb_textedit_delete(str, state, prev, state->cursor - prev);
state->cursor = prev;
}
}
state->has_preferred_x = 0;
@@ -1109,8 +1141,8 @@ retry:
static void stb_textedit_flush_redo(StbUndoState *state)
{
state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT;
state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT;
state->redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT;
state->redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT;
}
// discard the oldest entry in the undo list
@@ -1122,13 +1154,13 @@ static void stb_textedit_discard_undo(StbUndoState *state)
int n = state->undo_rec[0].insert_length, i;
// delete n characters from all other records
state->undo_char_point -= n;
STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE)));
IMSTB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(IMSTB_TEXTEDIT_CHARTYPE)));
for (i=0; i < state->undo_point; ++i)
if (state->undo_rec[i].char_storage >= 0)
state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it
}
--state->undo_point;
STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0])));
IMSTB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0])));
}
}
@@ -1138,7 +1170,7 @@ static void stb_textedit_discard_undo(StbUndoState *state)
// fill up even though the undo buffer didn't
static void stb_textedit_discard_redo(StbUndoState *state)
{
int k = STB_TEXTEDIT_UNDOSTATECOUNT-1;
int k = IMSTB_TEXTEDIT_UNDOSTATECOUNT-1;
if (state->redo_point <= k) {
// if the k'th undo state has characters, clean those up
@@ -1146,7 +1178,7 @@ static void stb_textedit_discard_redo(StbUndoState *state)
int n = state->undo_rec[k].insert_length, i;
// move the remaining redo character data to the end of the buffer
state->redo_char_point += n;
STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE)));
IMSTB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((IMSTB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(IMSTB_TEXTEDIT_CHARTYPE)));
// adjust the position of all the other records to account for above memmove
for (i=state->redo_point; i < k; ++i)
if (state->undo_rec[i].char_storage >= 0)
@@ -1154,12 +1186,12 @@ static void stb_textedit_discard_redo(StbUndoState *state)
}
// now move all the redo records towards the end of the buffer; the first one is at 'redo_point'
// [DEAR IMGUI]
size_t move_size = (size_t)((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0]));
size_t move_size = (size_t)((IMSTB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0]));
const char* buf_begin = (char*)state->undo_rec; (void)buf_begin;
const char* buf_end = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end;
IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin);
IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end);
STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size);
IMSTB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size);
// now move redo_point to point to the new one
++state->redo_point;
@@ -1173,32 +1205,32 @@ static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numch
// if we have no free records, we have to make room, by sliding the
// existing records down
if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
if (state->undo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)
stb_textedit_discard_undo(state);
// if the characters to store won't possibly fit in the buffer, we can't undo
if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) {
if (numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT) {
state->undo_point = 0;
state->undo_char_point = 0;
return NULL;
}
// if we don't have enough free characters in the buffer, we have to make room
while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT)
while (state->undo_char_point + numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT)
stb_textedit_discard_undo(state);
return &state->undo_rec[state->undo_point++];
}
static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len)
static IMSTB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len)
{
StbUndoRecord *r = stb_text_create_undo_record(state, insert_len);
if (r == NULL)
return NULL;
r->where = pos;
r->insert_length = (STB_TEXTEDIT_POSITIONTYPE) insert_len;
r->delete_length = (STB_TEXTEDIT_POSITIONTYPE) delete_len;
r->insert_length = (IMSTB_TEXTEDIT_POSITIONTYPE) insert_len;
r->delete_length = (IMSTB_TEXTEDIT_POSITIONTYPE) delete_len;
if (insert_len == 0) {
r->char_storage = -1;
@@ -1210,7 +1242,7 @@ static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos,
}
}
static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
{
StbUndoState *s = &state->undostate;
StbUndoRecord u, *r;
@@ -1237,7 +1269,7 @@ static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
// characters stored for *undoing* don't leave room for redo
// if the last is true, we have to bail
if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) {
if (s->undo_char_point + u.delete_length >= IMSTB_TEXTEDIT_UNDOCHARCOUNT) {
// the undo records take up too much character space; there's no space to store the redo characters
r->insert_length = 0;
} else {
@@ -1246,7 +1278,7 @@ static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
// there's definitely room to store the characters eventually
while (s->undo_char_point + u.delete_length > s->redo_char_point) {
// should never happen:
if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)
return;
// there's currently not enough room, so discard a redo record
stb_textedit_discard_redo(s);
@@ -1278,11 +1310,11 @@ static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
s->redo_point--;
}
static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
{
StbUndoState *s = &state->undostate;
StbUndoRecord *u, r;
if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)
return;
// we need to do two things: apply the redo record, and create an undo record
@@ -1334,20 +1366,20 @@ static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int le
stb_text_createundo(&state->undostate, where, 0, length);
}
static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length)
static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length)
{
int i;
STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0);
IMSTB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0);
if (p) {
for (i=0; i < length; ++i)
p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);
}
}
static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length)
static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length)
{
int i;
STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length);
IMSTB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length);
if (p) {
for (i=0; i < old_length; ++i)
p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);
@@ -1359,8 +1391,8 @@ static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_lin
{
state->undostate.undo_point = 0;
state->undostate.undo_char_point = 0;
state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT;
state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT;
state->undostate.redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT;
state->undostate.redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT;
state->select_end = state->select_start = 0;
state->cursor = 0;
state->has_preferred_x = 0;
@@ -1383,16 +1415,16 @@ static void stb_textedit_initialize_state(STB_TexteditState *state, int is_singl
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len)
static int stb_textedit_paste(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE const *ctext, int len)
{
return stb_textedit_paste_internal(str, state, (STB_TEXTEDIT_CHARTYPE *) ctext, len);
return stb_textedit_paste_internal(str, state, (IMSTB_TEXTEDIT_CHARTYPE *) ctext, len);
}
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
#endif//STB_TEXTEDIT_IMPLEMENTATION
#endif//IMSTB_TEXTEDIT_IMPLEMENTATION
/*
------------------------------------------------------------------------------
+1 -1
View File
@@ -656,7 +656,7 @@ STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h
STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip);
// If skip != 0, this tells stb_truetype to skip any codepoints for which
// there is no corresponding glyph. If skip=0, which is the default, then
// codepoints without a glyph recived the font's "missing character" glyph,
// codepoints without a glyph received the font's "missing character" glyph,
// typically an empty box by convention.
STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above
+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)
+5 -4
View File
@@ -60,7 +60,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() {
@@ -493,7 +493,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 +652,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;
+18 -2
View File
@@ -100,9 +100,25 @@ namespace games::iidx {
*phkResult = DEVICE_ASIO_REG_HANDLE;
log_info("iidx::asio", "replacing '{}' with '{}'", lpSubKey, ASIO_DRIVER.value());
return RegOpenKeyExA_orig(real_asio_reg_handle, ASIO_DRIVER.value().c_str(), ulOptions, samDesired,
const auto result = RegOpenKeyExA_orig(
real_asio_reg_handle,
ASIO_DRIVER.value().c_str(),
ulOptions,
samDesired,
&real_asio_device_reg_handle);
if (result != ERROR_SUCCESS) {
log_warning(
"iidx::asio",
"failed to open registry subkey '{}', error=0x{:x}",
ASIO_DRIVER.value().c_str(), result);
log_warning(
"iidx::asio",
"due to improper ASIO setting, game will likely fail to launch; double check -iidxasio and the registry",
ASIO_DRIVER.value().c_str(), result);
}
return result;
}
}
+20 -12
View File
@@ -35,6 +35,7 @@
#include "bc/io.h"
#include "ccj/io.h"
#include "qks/io.h"
#include "mfg/io.h"
namespace games {
@@ -305,6 +306,13 @@ namespace games {
buttons.insert({ qks, qks::get_buttons() });
buttons_help.insert({ qks, qks::get_buttons_help() });
file_hints[qks].emplace_back("game/uks.exe");
// Mahjong Fight Girl
const std::string mfg("Mahjong Fight Girl");
games.push_back(mfg);
buttons.insert({ mfg, mfg::get_buttons() });
buttons_help.insert({ mfg, mfg::get_buttons_help() });
file_hints[mfg].emplace_back("game/MFGClient_Data");
}
const std::vector<std::string> &get_games() {
@@ -437,22 +445,22 @@ namespace games {
vkey_defaults.push_back(VK_F12);
names.emplace_back("Toggle Camera Control");
vkey_defaults.push_back(0xFF);
names.emplace_back("Player 1 PIN Macro");
vkey_defaults.push_back(0xFF);
names.emplace_back("Player 2 PIN Macro");
vkey_defaults.push_back(0xFF);
names.emplace_back("Screen Resize");
vkey_defaults.push_back(0xFF);
names.emplace_back("Screen Resize Scene 1");
vkey_defaults.push_back(0xFF);
names.emplace_back("Screen Resize Scene 2");
vkey_defaults.push_back(0xFF);
names.emplace_back("Screen Resize Scene 3");
vkey_defaults.push_back(0xFF);
names.emplace_back("Screen Resize Scene 4");
vkey_defaults.push_back(0xFF);
names.emplace_back("Force Exit Game");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Activate");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Cancel");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Up");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Down");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Left");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Right");
vkey_defaults.push_back(0xFF);
names.emplace_back("Hotkey Enable 1");
vkey_defaults.push_back(0xFF);
names.emplace_back("Hotkey Enable 2");
+6 -6
View File
@@ -22,14 +22,14 @@ namespace games {
ToggleScreenResize,
ToggleOverlay,
ToggleCameraControl,
TriggerPinMacroP1,
TriggerPinMacroP2,
ScreenResize,
ScreenResizeScene1,
ScreenResizeScene2,
ScreenResizeScene3,
ScreenResizeScene4,
SuperExit,
NavigatorActivate,
NavigatorCancel,
NavigatorUp,
NavigatorDown,
NavigatorLeft,
NavigatorRight,
HotkeyEnable1,
HotkeyEnable2,
HotkeyToggle,
+385
View File
@@ -0,0 +1,385 @@
#include "bi2a_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"
namespace games::mfg {
/*
* class definitions
*/
struct AIO_SCI_COMM {
};
struct AIO_NMGR_IOB {
};
struct AIO_IOB_BI2A_VFG {
};
struct AIO_IOB_BI2A_VFG__INPUTDATA {
uint8_t data[16];
};
struct AIO_IOB_BI2A_VFG__OUTPUTDATA {
uint8_t data[40];
};
struct AIO_IOB_BI2A_VFG__INPUT
{
uint8_t bPcPowerCheck;
uint8_t CoinCount;
uint8_t bTest;
uint8_t bService;
uint8_t bCoinSw;
uint8_t bCoinJam;
uint8_t bCabinet;
uint8_t bHPDetect;
uint8_t bRecDetect;
uint8_t bStart;
};
struct AIO_IOB_BI2A_VFG__DEVSTATUS
{
uint8_t MechType;
uint8_t bIsBi2a;
uint8_t IoCounter;
AIO_IOB_BI2A_VFG__INPUT Input;
AIO_IOB_BI2A_VFG__INPUTDATA InputData;
AIO_IOB_BI2A_VFG__OUTPUTDATA OutputData;
};
/*
* typedefs
*/
// libaio-iob_video.dll
typedef AIO_SCI_COMM* (__fastcall *aioIobBi2a_OpenSciUsbCdc_t)(uint32_t i_SerialNumber);
typedef AIO_IOB_BI2A_VFG* (__fastcall *aioIobBi2aVFG_Create_t)(AIO_NMGR_IOB *i_pNodeMgr, uint32_t i_DevId);
typedef void (__fastcall *aioIobBi2aVFG_SetMechType_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl, uint32_t i_MechType);
typedef void (__fastcall *aioIobBi2aVFG_GetDeviceStatus_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl,
AIO_IOB_BI2A_VFG__DEVSTATUS *o_pDevStatus,
uint32_t i_cbDevStatus);
typedef void (__fastcall *aioIobBi2aVFG_SetWatchDogTimer_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint16_t i_Count);
typedef void (__fastcall *aioIobBi2aVFG_ControlCoinBlocker_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Slot,
bool i_bOpen);
typedef void (__fastcall *aioIobBi2aVFG_AddCounter_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Counter,
uint32_t i_Count);
typedef void (__fastcall *aioIobBi2aVFG_SetAmpVolume_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Amp,
uint32_t i_Volume);
typedef void (__fastcall *aioIobBi2aVFG_EnableUsbCharger_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, bool i_bEnable);
typedef void (__fastcall *aioIobBi2aVFG_SetLamp_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Lamp, uint8_t i_Bright);
typedef void (__fastcall *aioIobBi2aVFG_SetIccrLed_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_RGB);
typedef void (__fastcall *aioIobBi2aVFG_SetLedData_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint8_t *i_pData,
uint32_t i_cbData);
// libaio-iob.dll
typedef AIO_NMGR_IOB* (__fastcall *aioNMgrIob_Create_t)(AIO_SCI_COMM* i_pSci, uint32_t i_bfMode);
// libaio.dll
typedef void (__fastcall *aioNodeMgr_Destroy_t)(AIO_NMGR_IOB *i_pNodeMgr);
typedef int32_t (__fastcall *aioNodeMgr_GetState_t)(AIO_NMGR_IOB *i_pNodeMgr);
typedef bool (__fastcall *aioNodeMgr_IsReady_t)(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State);
typedef bool (__fastcall *aioNodeMgr_IsError_t)(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State);
typedef void (__fastcall *aioNodeCtl_Destroy_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl);
typedef int32_t (__fastcall *aioNodeCtl_GetState_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl);
typedef bool (__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl, int32_t i_State);
typedef bool (__fastcall *aioNodeCtl_IsError_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl, int32_t i_State);
/*
* function pointers
*/
// libaio-iob_video.dll
static aioIobBi2a_OpenSciUsbCdc_t aioIobBi2a_OpenSciUsbCdc_orig = nullptr;
static aioIobBi2aVFG_Create_t aioIobBi2aVFG_Create_orig = nullptr;
static aioIobBi2aVFG_SetMechType_t aioIobBi2aVFG_SetMechType_orig = nullptr;
static aioIobBi2aVFG_GetDeviceStatus_t aioIobBi2aVFG_GetDeviceStatus_orig = nullptr;
static aioIobBi2aVFG_SetWatchDogTimer_t aioIobBi2aVFG_SetWatchDogTimer_orig = nullptr;
static aioIobBi2aVFG_ControlCoinBlocker_t aioIobBi2aVFG_ControlCoinBlocker_orig = nullptr;
static aioIobBi2aVFG_AddCounter_t aioIobBi2aVFG_AddCounter_orig = nullptr;
static aioIobBi2aVFG_SetAmpVolume_t aioIobBi2aVFG_SetAmpVolume_orig = nullptr;
static aioIobBi2aVFG_EnableUsbCharger_t aioIobBi2aVFG_EnableUsbCharger_orig = nullptr;
static aioIobBi2aVFG_SetLamp_t aioIobBi2aVFG_SetLamp_orig = nullptr;
static aioIobBi2aVFG_SetIccrLed_t aioIobBi2aVFG_SetIccrLed_orig = nullptr;
static aioIobBi2aVFG_SetLedData_t aioIobBi2aVFG_SetLedData_orig = nullptr;
// libaio-iob.dll
static aioNMgrIob_Create_t aioNMgrIob_Create_orig = nullptr;
// libaio.dll
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_SCI_COMM *aioSciComm;
static AIO_NMGR_IOB *aioNmgrIob;
static AIO_IOB_BI2A_VFG *aioIobBi2aVfg;
static uint8_t mechType = 0;
static uint8_t count = 0;
/*
* implementations
*/
static AIO_SCI_COMM* __fastcall aioIob2Bi2a_OpenSciUsbCdc(uint32_t i_SerialNumber) {
log_info("bi2a_hook", "aioIob2Bi2a_OpenSciUsbCdc hook hit");
aioSciComm = new AIO_SCI_COMM;
return aioSciComm;
}
static AIO_IOB_BI2A_VFG* __fastcall aioIobBi2aVFG_Create(AIO_NMGR_IOB *i_pNodeMgr, uint32_t i_DevId) {
if (i_pNodeMgr == aioNmgrIob) {
log_info("bi2a_hook", "node created");
aioIobBi2aVfg = new AIO_IOB_BI2A_VFG;
return aioIobBi2aVfg;
} else {
return aioIobBi2aVFG_Create_orig(i_pNodeMgr, i_DevId);
}
}
void __fastcall aioIobBi2aVFG_SetMechType(AIO_IOB_BI2A_VFG *i_pNodeCtl, uint32_t i_MechType) {
if (i_pNodeCtl == aioIobBi2aVfg) {
mechType = i_MechType;
} else {
return aioIobBi2aVFG_SetMechType_orig(i_pNodeCtl, i_MechType);
}
}
void __fastcall aioIobBi2aVFG_GetDeviceStatus(AIO_IOB_BI2A_VFG* i_pNodeCtl,
AIO_IOB_BI2A_VFG__DEVSTATUS *o_pDevStatus,
uint32_t i_cbDevStatus) {
RI_MGR->devices_flush_output();
if (i_pNodeCtl != aioIobBi2aVfg) {
return aioIobBi2aVFG_GetDeviceStatus_orig(i_pNodeCtl, o_pDevStatus, i_cbDevStatus);
}
memset(o_pDevStatus, 0x00, sizeof(AIO_IOB_BI2A_VFG__DEVSTATUS));
o_pDevStatus->MechType = mechType;
o_pDevStatus->bIsBi2a = 1;
o_pDevStatus->IoCounter = count++;
auto &buttons = get_buttons();
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]))
o_pDevStatus->Input.bTest = 1;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]))
o_pDevStatus->Input.bService = 1;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]))
o_pDevStatus->Input.bCoinSw = 1;
o_pDevStatus->Input.CoinCount = eamuse_coin_get_stock();
}
void __fastcall aioIobBi2aVFG_SetWatchDogTimer(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint16_t i_Count) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_SetWatchDogTimer_orig(i_pNodeCtl, i_Count);
}
}
void __fastcall aioIobBi2aVFG_ControlCoinBlocker(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Slot, bool i_bOpen) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_ControlCoinBlocker_orig(i_pNodeCtl, i_Slot, i_bOpen);
}
}
void __fastcall aioIobBi2aVFG_AddCounter(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Counter, uint32_t i_Count) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_AddCounter_orig(i_pNodeCtl, i_Counter, i_Count);
}
}
void __fastcall aioIobBi2aVFG_SetAmpVolume(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Amp, uint32_t i_Volume) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_SetAmpVolume_orig(i_pNodeCtl, i_Amp, i_Volume);
}
}
void __fastcall aioIobBi2aVFG_EnableUsbCharger(AIO_IOB_BI2A_VFG* i_pNodeCtl, bool i_bEnable) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_EnableUsbCharger_orig(i_pNodeCtl, i_bEnable);
}
}
void __fastcall aioIobBi2aVFG_SetLamp(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Lamp, uint8_t i_Bright) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_SetLamp_orig(i_pNodeCtl, i_Lamp, i_Bright);
}
}
void __fastcall aioIobBi2aVFG_SetIccrLed(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_RGB) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_SetIccrLed_orig(i_pNodeCtl, i_RGB);
}
}
void __fastcall aioIobBi2aVFG_SetLedData(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint8_t *i_pData, uint32_t i_cbData) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_SetLedData_orig(i_pNodeCtl, i_pData, i_cbData);
}
}
static AIO_NMGR_IOB* __fastcall aioNMgrIob_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
if (i_pSci == aioSciComm) {
aioNmgrIob = new AIO_NMGR_IOB;
return aioNmgrIob;
} else {
return aioNMgrIob_Create_orig(i_pSci, i_bfMode);
}
}
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob) {
delete aioNmgrIob;
aioNmgrIob = nullptr;
} else {
return aioNodeMgr_Destroy_orig(i_pNodeMgr);
}
}
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob) {
return 1;
} else {
return aioNodeMgr_GetState_orig(i_pNodeMgr);
}
}
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob) {
return true;
} else {
return aioNodeMgr_IsReady_orig(i_pNodeMgr, i_State);
}
}
static bool __fastcall aioNodeMgr_IsError(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob) {
return false;
} else {
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
}
}
static void __fastcall aioNodeCtl_Destroy(AIO_IOB_BI2A_VFG *i_pNodeCtl) {
if (i_pNodeCtl == aioIobBi2aVfg) {
delete aioIobBi2aVfg;
aioIobBi2aVfg = nullptr;
} else {
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
}
}
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB_BI2A_VFG *i_pNodeCtl) {
if (i_pNodeCtl == aioIobBi2aVfg) {
return 1;
} else {
return aioNodeCtl_GetState_orig(i_pNodeCtl);
}
}
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB_BI2A_VFG *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIobBi2aVfg) {
return true;
} else {
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
}
}
static bool __fastcall aioNodeCtl_IsError(AIO_IOB_BI2A_VFG *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIobBi2aVfg) {
return false;
} else {
return aioNodeCtl_IsError_orig(i_pNodeCtl, i_State);
}
}
void bi2a_hook_init() {
// avoid double init
static bool initialized = false;
if (initialized) {
return;
} else {
initialized = true;
}
// announce
log_info("bi2a_hook", "init");
// libaio-iob_video.dll
const auto libaioIobVideoDll = "libaio-iob_video.dll";
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2a_OpenSciUsbCdc",
aioIob2Bi2a_OpenSciUsbCdc, &aioIobBi2a_OpenSciUsbCdc_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_Create",
aioIobBi2aVFG_Create, &aioIobBi2aVFG_Create_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetMechType",
aioIobBi2aVFG_SetMechType, &aioIobBi2aVFG_SetMechType_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_GetDeviceStatus",
aioIobBi2aVFG_GetDeviceStatus, &aioIobBi2aVFG_GetDeviceStatus_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetWatchDogTimer",
aioIobBi2aVFG_SetWatchDogTimer, &aioIobBi2aVFG_SetWatchDogTimer_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_ControlCoinBlocker",
aioIobBi2aVFG_ControlCoinBlocker, &aioIobBi2aVFG_ControlCoinBlocker_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_AddCounter",
aioIobBi2aVFG_AddCounter, &aioIobBi2aVFG_AddCounter_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetAmpVolume",
aioIobBi2aVFG_SetAmpVolume, &aioIobBi2aVFG_SetAmpVolume_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_EnableUsbCharger",
aioIobBi2aVFG_EnableUsbCharger, &aioIobBi2aVFG_EnableUsbCharger_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetLamp",
aioIobBi2aVFG_SetLamp, &aioIobBi2aVFG_SetLamp_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetIccrLed",
aioIobBi2aVFG_SetIccrLed, &aioIobBi2aVFG_SetIccrLed_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetLedData",
aioIobBi2aVFG_SetLedData, &aioIobBi2aVFG_SetLedData_orig);
// libaio-iob.dll
const auto libaioIobDll = "libaio-iob.dll";
execexe::trampoline_try(libaioIobDll, "aioNMgrIob_Create",
aioNMgrIob_Create, &aioNMgrIob_Create_orig);
// libaio.dll
const auto libaioDll = "libaio.dll";
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::mfg {
void bi2a_hook_init();
}
+22
View File
@@ -0,0 +1,22 @@
#include "io.h"
std::vector<Button> &games::mfg::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Mahjong Fight Girl");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech"
);
}
return buttons;
}
std::string games::mfg::get_buttons_help() {
return "";
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::mfg {
namespace Buttons {
enum {
Service,
Test,
CoinMech
};
}
// getters
std::vector<Button> &get_buttons();
std::string get_buttons_help();
}
+74
View File
@@ -0,0 +1,74 @@
#include "mfg.h"
#include <format>
#include "util/fileutils.h"
#include "util/unity_player.h"
#include "util/utils.h"
#include "util/execexe.h"
#include "acioemu/handle.h"
#include "misc/wintouchemu.h"
#include "hooks/graphics/graphics.h"
#include "bi2a_hook.h"
namespace games::mfg {
std::string MFG_INJECT_ARGS = "";
std::string MFG_CABINET_TYPE = "HG";
bool MFG_NO_IO = false;
static acioemu::ACIOHandle *acioHandle = nullptr;
static std::wstring portName;
void MFGGame::attach() {
Game::attach();
// create required files
fileutils::dir_create_recursive("dev/raw/log");
fileutils::bin_write("dev/raw/log/output_log.txt", nullptr, 0);
SetEnvironmentVariableA("VFG_CABINET_TYPE", MFG_CABINET_TYPE.c_str());
// add card reader
portName = MFG_CABINET_TYPE == "UKS" ? L"\\\\.\\COM1" : L"\\\\.\\COM3";
acioHandle = new acioemu::ACIOHandle(portName.c_str(), 1);
devicehook_init_trampoline();
devicehook_add(acioHandle);
execexe::init();
execexe::init_port_hook(portName, acioHandle);
if (GRAPHICS_SHOW_CURSOR) {
unity_utils::force_show_cursor(true);
}
unity_utils::set_args(
std::format("{} {}{}",
GetCommandLineA(),
MFG_INJECT_ARGS,
unity_utils::get_unity_player_args()));
}
void MFGGame::post_attach() {
Game::post_attach();
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 (!MFG_NO_IO) {
// insert BI2* hooks
if (MFG_CABINET_TYPE == "UKS") {
log_fatal("mfg", "UKS io is not supported");
} else {
bi2a_hook_init();
}
}
}
void MFGGame::detach() {
Game::detach();
devicehook_dispose();
}
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "games/game.h"
namespace games::mfg {
extern std::string MFG_INJECT_ARGS;
extern std::string MFG_CABINET_TYPE;
extern bool MFG_NO_IO;
class MFGGame : public games::Game {
public:
MFGGame() : Game("Mahjong Fight Girl") {}
virtual void attach() override;
virtual void post_attach() override;
virtual void detach() override;
};
}
+2
View File
@@ -71,6 +71,8 @@ std::vector<Light> &games::museca::get_lights() {
GameAPI::Lights::sortLights(
&lights,
"Start",
"Keypad",
"Title R",
"Title G",
"Title B",
+2
View File
@@ -46,6 +46,8 @@ namespace games::museca {
// all lights in correct order
namespace Lights {
enum {
Start,
Keypad,
TitleR,
TitleG,
TitleB,
+1 -1
View File
@@ -63,7 +63,7 @@ namespace games::qma {
touch_attach_dx_hook();
// cursor
if (!is_touch_available()) {
if (!is_touch_available("QMATouchDevice::open")) {
ShowCursor(true);
}
}
+1 -1
View File
@@ -86,7 +86,7 @@ bool games::rb::ReflecBeatTouchDeviceHandle::open(LPCWSTR lpFileName) {
}
// show cursor on window if mouse is used
if (!is_touch_available()) {
if (!is_touch_available("ReflecBeatTouchDeviceHandle::open")) {
ShowCursor(TRUE);
}
+1 -1
View File
@@ -72,7 +72,7 @@ public:
touch_attach_dx_hook();
// cursor
if (!is_touch_available()) {
if (!is_touch_available("TwTouchDevice::open")) {
ShowCursor(true);
}
}
+18 -2
View File
@@ -79,9 +79,25 @@ namespace games::sdvx {
*phkResult = DEVICE_ASIO_REG_HANDLE;
log_info("sdvx::asio", "replacing '{}' with '{}'", lpSubKey, ASIO_DRIVER.value());
return RegOpenKeyExA_orig(real_asio_reg_handle, ASIO_DRIVER.value().c_str(), ulOptions, samDesired,
const auto result = RegOpenKeyExA_orig(
real_asio_reg_handle,
ASIO_DRIVER.value().c_str(),
ulOptions,
samDesired,
&real_asio_device_reg_handle);
if (result != ERROR_SUCCESS) {
log_warning(
"sdvx::asio",
"failed to open registry subkey '{}', error=0x{:x}",
ASIO_DRIVER.value().c_str(), result);
log_warning(
"sdvx::asio",
"due to improper ASIO setting, game will likely fall back to WASAPI; double check -sdvxasio and the registry",
ASIO_DRIVER.value().c_str(), result);
}
return result;
}
}
+3 -1
View File
@@ -11,7 +11,9 @@ namespace games::sdvx {
enum SdvxOverlayPosition {
SDVX_OVERLAY_TOP,
SDVX_OVERLAY_MIDDLE,
SDVX_OVERLAY_BOTTOM
SDVX_OVERLAY_BOTTOM,
SDVX_OVERLAY_BOTTOM_LEFT,
SDVX_OVERLAY_BOTTOM_RIGHT
};
// settings
@@ -680,7 +680,29 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
// dump presentation parameters
for (size_t i = 0; i < num_adapters; i++) {
const auto *params = &pPresentationParameters[i];
auto *params = &pPresentationParameters[i];
if (!GRAPHICS_WINDOWED && i == 0) {
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
log_misc(
"graphics::d3d9",
"use custom resolution {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
log_misc(
"graphics::d3d9",
"swap full orientation {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
params->BackBufferHeight, params->BackBufferWidth);
std::swap(params->BackBufferWidth, params->BackBufferHeight);
}
}
log_info("graphics::d3d9",
"D3D9 presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, "
@@ -846,7 +868,29 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
}
for (size_t i = 0; i < num_adapters; i++) {
const auto *params = &pPresentationParameters[i];
auto *params = &pPresentationParameters[i];
if (!GRAPHICS_WINDOWED && i == 0) {
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
log_misc(
"graphics::d3d9",
"use custom resolution {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
log_misc(
"graphics::d3d9",
"swap full orientation {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
params->BackBufferHeight, params->BackBufferWidth);
std::swap(params->BackBufferWidth, params->BackBufferHeight);
}
}
log_info("graphics::d3d9",
"D3D9Ex presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, "
@@ -870,7 +914,16 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
}
if (pFullscreenDisplayMode) {
for (size_t i = 0; i < num_adapters; i++) {
const auto *fullscreen_display_mode = &pFullscreenDisplayMode[i];
auto *fullscreen_display_mode = &pFullscreenDisplayMode[i];
if (!GRAPHICS_WINDOWED && i == 0) {
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height);
}
}
log_info("graphics::d3d9",
"D3D9Ex fullscreen display mode for adapter {}: Width: {}, Height: {}, RefreshRate: {}, "
@@ -7,6 +7,7 @@
#include "hooks/graphics/graphics.h"
#include "overlay/overlay.h"
#include "util/flags_helper.h"
#include "util/utils.h"
#include "cfg/screen_resize.h"
#include "d3d9_backend.h"
@@ -692,88 +693,181 @@ static IDirect3DSurface9 *backbuffer = nullptr;
static LPDIRECT3DSWAPCHAIN9 mSwapChain = nullptr;
static IDirect3DTexture9* tex;
static UINT topSurface_width = 0;
static UINT topSurface_height = 0;
static float topSurface_aspect_ratio = 1.f;
void SurfaceHook(IDirect3DDevice9 *pReal) {
// log_misc("graphics::d3d9", "SurfaceHook called");
D3DPRESENT_PARAMETERS param {};
pReal->GetSwapChain(0, &mSwapChain);
mSwapChain->GetPresentParameters(&param);
// phase 0 - create a surface that has the same aspect ratio as the original back buffer
// but larger in size. this is needed to ensure that when the user zooms out, we have
// sufficient buffer space (black border) around the original image
//
// (only done once)
if (!topSurface) {
if (pReal->CreateTexture(4096, 4096, 1,
topSurface_width = param.BackBufferWidth * 3;
topSurface_height = param.BackBufferHeight * 3;
topSurface_aspect_ratio = (float)topSurface_width / (float)topSurface_height;
if (pReal->CreateTexture(topSurface_width, topSurface_height, 1,
D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8,
D3DPOOL_DEFAULT, &tex, NULL) != D3D_OK) {
log_misc("graphics::d3d9", "create texture failed");
log_warning("graphics::d3d9", "SurfaceHook - create texture failed");
}
log_misc("graphics::d3d9", "Backbuffer: {} {} {}",
param.BackBufferWidth, param.BackBufferHeight, param.BackBufferCount);
tex->GetSurfaceLevel(0, &topSurface);
log_misc(
"graphics::d3d9", "SurfaceHook - Backbuffer: {} {} {}",
param.BackBufferWidth, param.BackBufferHeight, param.BackBufferCount);
tex->GetSurfaceLevel(0, &topSurface);
if (mSwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &backbuffer) != D3D_OK) {
log_misc("graphics::d3d9", "GetBackBuffer failed");
log_warning("graphics::d3d9", "SurfaceHook - GetBackBuffer failed");
}
}
const int rectLeft = 1024;
const int rectTop = 576;
// pre-calculate dimensions used for phase 1
const int rectLeft = param.BackBufferWidth;
const int rectTop = param.BackBufferHeight;
const int w = param.BackBufferWidth;
const int h = param.BackBufferHeight;
D3DLOCKED_RECT rect;
HRESULT hr;
topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
// phase 1 - copy the original back buffer onto the new surface.
// we draw it 1:1 in the center of the surface.
// if orientation is swapped, fix up the rect and draw it in the center.
//
// for this phase we always render with the same rect so that it's always overwritten
// by a new frame on the same spot.
RECT originRect {
rectLeft,
rectTop,
(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);
originRect.right = originRect.left + GRAPHICS_FS_ORIGINAL_WIDTH;
originRect.bottom = originRect.top + GRAPHICS_FS_ORIGINAL_HEIGHT;
}
// log_misc(
// "graphics::d3d9", "originRect: {} {} {} {}",
// originRect.left, originRect.top, originRect.right, originRect.bottom);
hr = pReal->StretchRect(
backbuffer, nullptr,
topSurface, &originRect,
D3DTEXF_LINEAR);
if (hr != D3D_OK) {
log_warning(
"graphics::d3d9",
"SurfaceHook - StretchRect backbuffer failed, rect: {} {} {} {}",
originRect.left, originRect.top, originRect.right, originRect.bottom);
}
topSurface->UnlockRect();
// phase 2 - viewport calculation - do the actual zoom / offset math
// figure out what region of the surface to copy back to the back buffer.
RECT targetRect {
rectLeft,
rectTop,
(LONG)(rectLeft + w),
(LONG)(rectTop + h),
};
if (cfg::SCREENRESIZE->enable_screen_resize) {
auto& scene = cfg::SCREENRESIZE->scene_settings[cfg::SCREENRESIZE->screen_resize_current_scene];
auto w_new = w / scene.scale_x;
auto h_new = h / scene.scale_y;
// stretch to add top/left offset to avoid going negative
topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
auto hr = pReal->StretchRect(
backbuffer, nullptr,
topSurface, &targetRect,
D3DTEXF_LINEAR);
// make sure the scaling is within bounds
if (cfg::SCREENRESIZE->client_keep_aspect_ratio) {
if (w_new > topSurface_width || h_new > topSurface_height) {
w_new = topSurface_width;
h_new = topSurface_height;
}
} else {
w_new = MIN(w_new, topSurface_width);
h_new = MIN(h_new, topSurface_height);
}
if (hr != D3D_OK) {
log_misc("graphics::d3d9", "StretchRect backbuffer failed");
}
topSurface->UnlockRect();
targetRect.left = (topSurface_width / 2) - (w_new / 2) - scene.offset_x;
targetRect.top = (topSurface_height / 2) - (h_new / 2) - scene.offset_y;
targetRect.right = targetRect.left + w_new;
targetRect.bottom = targetRect.top + h_new;
// do the actual zoom / offset math
if (cfg::SCREENRESIZE->centered) {
targetRect.right = (w + rectLeft) / cfg::SCREENRESIZE->scale_x;
targetRect.bottom = (h + rectTop) / cfg::SCREENRESIZE->scale_y;
const LONG deltaH = ((targetRect.bottom - targetRect.top) - h) / 2;
const LONG deltaW = ((targetRect.right - targetRect.left) - w) / 2;
targetRect.top -= deltaH;
targetRect.bottom -= deltaH;
targetRect.left -= deltaW;
targetRect.right -= deltaW;
} else {
targetRect.left -= cfg::SCREENRESIZE->offset_x;
targetRect.top += cfg::SCREENRESIZE->offset_y;
targetRect.right = -cfg::SCREENRESIZE->offset_x;
targetRect.right += (w + rectLeft) / cfg::SCREENRESIZE->scale_x;
targetRect.bottom = cfg::SCREENRESIZE->offset_y;
targetRect.bottom += (h + rectTop) / cfg::SCREENRESIZE->scale_y;
// boundary check to prevent StretchRect failures
if (targetRect.left < 0) {
targetRect.left = 0;
targetRect.right = w_new;
} else if (targetRect.right > (LONG)topSurface_width) {
targetRect.left = topSurface_width - w_new;
targetRect.right = topSurface_width;
}
if (targetRect.top < 0) {
targetRect.top = 0;
targetRect.bottom = h_new;
} else if (targetRect.bottom > (LONG)topSurface_height) {
targetRect.top = topSurface_height - h_new;
targetRect.bottom = topSurface_height;
}
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
// increase the viewport to fit the image on screen (effectively, zoom out a little)
if (GRAPHICS_FS_ORIGINAL_WIDTH < GRAPHICS_FS_ORIGINAL_HEIGHT) {
// portrait game on landscape display
// height should match the original image
targetRect.top = originRect.top;
targetRect.bottom = originRect.bottom;
// width of viewport should be wider, effectively shrinking the image on x-axis when rendered
const auto w_new = GRAPHICS_FS_ORIGINAL_HEIGHT * topSurface_aspect_ratio;
targetRect.left = (topSurface_width / 2) - (w_new / 2);
targetRect.right = targetRect.left + w_new;
} else {
// landscape game on portrait display
targetRect.left = originRect.left;
targetRect.right = originRect.right;
// height of viewport should be taller, effectively shrinking the image on y-axis when rendered
const auto h_new = GRAPHICS_FS_ORIGINAL_WIDTH / topSurface_aspect_ratio;
targetRect.top = (topSurface_height / 2) - (h_new / 2);
targetRect.bottom = targetRect.top + h_new;
}
}
// draw to back buffer
// log_misc("graphics::d3d9", "targetRect: {} {} {} {}",
// targetRect.left, targetRect.top, targetRect.right, targetRect.bottom);
// phase 3 - draw the surface to back buffer
backbuffer->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
bool use_linear_filter = true;
if (cfg::SCREENRESIZE->enable_screen_resize) {
use_linear_filter = cfg::SCREENRESIZE->enable_linear_filter;
}
hr = pReal->StretchRect(
topSurface, &targetRect,
backbuffer, nullptr,
cfg::SCREENRESIZE->enable_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE);
use_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE);
backbuffer->UnlockRect();
if (hr != D3D_OK) {
log_misc("graphics::d3d9", "StretchRect targetRect failed");
log_warning(
"graphics::d3d9",
"SurfaceHook - StretchRect targetRect failed, you must reset image scaling to default values! rect: {} {} {} {}",
targetRect.left, targetRect.top, targetRect.right, targetRect.bottom);
}
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::EndScene() {
WRAP_DEBUG;
if (cfg::SCREENRESIZE->enable_screen_resize) {
if (cfg::SCREENRESIZE->enable_screen_resize || GRAPHICS_FS_ORIENTATION_SWAP) {
SurfaceHook(pReal);
}
+4
View File
@@ -67,6 +67,10 @@ graphics_dx9on12_state GRAPHICS_9_ON_12_STATE = DX9ON12_AUTO;
bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME = false;
bool SUBSCREEN_FORCE_REDRAW = false;
bool D3D9_DEVICE_HOOK_DISABLE = false;
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
bool GRAPHICS_FS_ORIENTATION_SWAP = false;
uint32_t GRAPHICS_FS_ORIGINAL_WIDTH = 0;
uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT = 0;
// settings
std::string GRAPHICS_DEVICEID = "PCI\\VEN_1002&DEV_7146";
+4
View File
@@ -35,6 +35,10 @@ extern UINT GRAPHICS_FORCE_REFRESH;
extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW;
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
extern bool GRAPHICS_FS_ORIENTATION_SWAP;
extern uint32_t GRAPHICS_FS_ORIGINAL_WIDTH;
extern uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT;
extern graphics_dx9on12_state GRAPHICS_9_ON_12_STATE;
extern bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME;
@@ -123,10 +123,6 @@ void graphics_load_windowed_parameters() {
}
log_debug("graphics-windowed", "graphics_load_windowed_parameters called");
const auto remove_spaces = [](const char& c) {
return c == ' ';
};
if (GRAPHICS_WINDOW_STYLE.has_value()) {
log_debug(
"graphics-windowed",
@@ -148,13 +144,11 @@ void graphics_load_windowed_parameters() {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_WINDOW_POS");
int32_t x, y;
auto s = GRAPHICS_WINDOW_POS.value();
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
if (sscanf(s.c_str(), "%d,%d", &x, &y) == 2) {
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(GRAPHICS_WINDOW_POS.value(), result)) {
cfg::SCREENRESIZE->enable_window_resize = true;
cfg::SCREENRESIZE->window_offset_x = x;
cfg::SCREENRESIZE->window_offset_y = y;
cfg::SCREENRESIZE->window_offset_x = result.first;
cfg::SCREENRESIZE->window_offset_y = result.second;
} else {
log_warning("graphics-windowed", "failed to parse -windowpos");
}
@@ -174,20 +168,16 @@ void graphics_load_windowed_subscreen_parameters() {
}
log_debug("graphics-windowed", "graphics_load_windowed_subscreen_parameters called");
const auto remove_spaces = [](const char& c) {
return c == ' ';
};
if (GRAPHICS_IIDX_WSUB_SIZE.has_value()) {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_IIDX_WSUB_SIZE");
uint32_t w, h;
auto s = GRAPHICS_IIDX_WSUB_SIZE.value();
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
if (sscanf(s.c_str(), "%u,%u", &w, &h) == 2) {
GRAPHICS_IIDX_WSUB_WIDTH = w;
GRAPHICS_IIDX_WSUB_HEIGHT = h;
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;
} else {
log_warning("graphics-windowed", "failed to parse -wsubsize");
}
@@ -197,12 +187,11 @@ void graphics_load_windowed_subscreen_parameters() {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_IIDX_WSUB_POS");
int32_t x, y;
auto s = GRAPHICS_IIDX_WSUB_POS.value();
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
if (sscanf(s.c_str(), "%d,%d", &x, &y) == 2) {
GRAPHICS_IIDX_WSUB_X = x;
GRAPHICS_IIDX_WSUB_Y = y;
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;
} else {
log_warning("graphics-windowed", "failed to parse -wsubpos");
}
+151 -41
View File
@@ -59,6 +59,7 @@
#include "games/ccj/ccj.h"
#include "games/ccj/trackball.h"
#include "games/qks/qks.h"
#include "games/mfg/mfg.h"
#include "games/museca/museca.h"
#include "hooks/avshook.h"
#include "hooks/audio/audio.h"
@@ -101,6 +102,7 @@
#include "util/sysutils.h"
#include "util/tapeled.h"
#include "util/time.h"
#include "util/utils.h"
#include "avs/ssl.h"
#include "nvapi/nvapi.h"
#include "hooks/graphics/nvapi_hook.h"
@@ -122,6 +124,8 @@ std::string LOG_FILE_PATH = "";
int LAUNCHER_ARGC = 0;
char **LAUNCHER_ARGV = nullptr;
std::unique_ptr<std::vector<Option>> LAUNCHER_OPTIONS;
std::mutex CARD_OVERRIDES_LOCK;
std::string CARD_OVERRIDES[2];
// sub-systems
@@ -143,6 +147,8 @@ static bool check_dll(const std::string &model) {
}
}
void update_msvcrt_args(int argc, char *argv[]);
int main_implementation(int argc, char *argv[]) {
// remember argv, argv
@@ -211,7 +217,9 @@ int main_implementation(int argc, char *argv[]) {
bool attach_bc = false;
bool attach_ccj = false;
bool attach_qks = false;
bool attach_mfg = false;
bool attach_museca = false;
bool show_cursor_if_no_touch = false;
// misc settings
size_t user_heap_size = 0;
@@ -434,7 +442,12 @@ int main_implementation(int argc, char *argv[]) {
games::sdvx::OVERLAY_POS = games::sdvx::SDVX_OVERLAY_TOP;
} else if (txt == "center") {
games::sdvx::OVERLAY_POS = games::sdvx::SDVX_OVERLAY_MIDDLE;
} else if (txt == "bottomleft") {
games::sdvx::OVERLAY_POS = games::sdvx::SDVX_OVERLAY_BOTTOM_LEFT;
} else if (txt == "bottomright") {
games::sdvx::OVERLAY_POS = games::sdvx::SDVX_OVERLAY_BOTTOM_RIGHT;
}
// else - bottom
}
if (options[launcher::Options::spice2x_SDVXSubRedraw].value_bool()) {
SUBSCREEN_FORCE_REDRAW = true;
@@ -600,6 +613,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::LoadQKSModule].value_bool()) {
attach_qks = true;
}
if (options[launcher::Options::LoadMFGModule].value_bool()) {
attach_mfg = true;
}
if (options[launcher::Options::LoadMusecaModule].value_bool()) {
attach_museca = true;
}
@@ -730,11 +746,22 @@ int main_implementation(int argc, char *argv[]) {
SetDllDirectoryW(MODULE_PATH.c_str());
}
if (options[launcher::Options::Player1Card].is_active()) {
std::lock_guard<std::mutex> lock(CARD_OVERRIDES_LOCK);
CARD_OVERRIDES[0] = options[launcher::Options::Player1Card].value_text();
}
if (options[launcher::Options::Player2Card].is_active()) {
std::lock_guard<std::mutex> lock(CARD_OVERRIDES_LOCK);
CARD_OVERRIDES[1] = options[launcher::Options::Player2Card].value_text();
}
if (options[launcher::Options::Player1PinMacro].is_active()) {
PIN_MACRO_ENABLED = true;
PIN_MACRO_VALUES[0] = options[launcher::Options::Player1PinMacro].value_text();
}
if (options[launcher::Options::Player2PinMacro].is_active()) {
PIN_MACRO_ENABLED = true;
PIN_MACRO_VALUES[1] = options[launcher::Options::Player2PinMacro].value_text();
}
for (auto &reader : options[launcher::Options::ICCAReaderPort].values_text()) {
static int reader_id = 0;
if (reader_id < 2) {
@@ -936,12 +963,9 @@ int main_implementation(int argc, char *argv[]) {
GRAPHICS_WINDOW_STYLE = options[launcher::Options::spice2x_WindowBorder].value_uint32();
}
if (options[launcher::Options::spice2x_WindowSize].is_active()) {
auto s = options[launcher::Options::spice2x_WindowSize].value_text();
uint32_t w, h;
const auto remove_spaces = [](const char& c) { return c == ' '; };
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
if (sscanf(s.c_str(), "%u,%u", &w, &h) == 2) {
GRAPHICS_WINDOW_SIZE = std::pair(w, h);
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(options[launcher::Options::spice2x_WindowSize].value_text(), result)) {
GRAPHICS_WINDOW_SIZE = result;
} else {
log_warning("launcher", "failed to parse -windowsize");
}
@@ -1017,6 +1041,15 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::QKSArgs].is_active()) {
games::qks::QKS_INJECT_ARGS = options[launcher::Options::QKSArgs].value_text();
}
if (options[launcher::Options::MFGArgs].is_active()) {
games::mfg::MFG_INJECT_ARGS = options[launcher::Options::MFGArgs].value_text();
}
if (options[launcher::Options::MFGCabType].is_active()) {
games::mfg::MFG_CABINET_TYPE = options[launcher::Options::MFGCabType].value_text();
}
if (options[launcher::Options::MFGNoIO].is_active()) {
games::mfg::MFG_NO_IO = options[launcher::Options::MFGNoIO].value_bool();
}
if (options[launcher::Options::spice2x_EnableSMXStage].value_bool()) {
rawinput::ENABLE_SMX_STAGE = true;
}
@@ -1085,6 +1118,8 @@ int main_implementation(int argc, char *argv[]) {
// log
#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
@@ -1128,6 +1163,30 @@ int main_implementation(int argc, char *argv[]) {
}
log_info("launcher", "arguments:\n{}", arguments.str());
if (options[launcher::Options::FullscreenResolution].is_active()) {
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(options[launcher::Options::FullscreenResolution].value_text(), result)) {
GRAPHICS_FS_CUSTOM_RESOLUTION = result;
} else {
log_warning("launcher", "failed to parse -forceres");
}
}
// // SDVXFullscreenLandscape disabled due to it messing with in-game camera angle
// // FullscreenOrientationFlip continues to live on, however.
//
// 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;
}
// deleted options
if (options[launcher::Options::OpenKFControl].value_bool()) {
log_fatal("launcher", "KFControl has been removed from spice2x; please use an older version.");
@@ -1243,11 +1302,7 @@ int main_implementation(int argc, char *argv[]) {
avs::game::DLL_NAME = "system.dll";
attach_io = true;
attach_shogikai = true;
// automatically show cursor when no touchscreen is available
if (!is_touch_available()) {
GRAPHICS_SHOW_CURSOR = true;
}
show_cursor_if_no_touch = true;
break;
}
@@ -1290,12 +1345,7 @@ int main_implementation(int argc, char *argv[]) {
// game crash fix
easrv_maint = false;
// automatically show cursor when no touchscreen is available
if (!is_touch_available()) {
GRAPHICS_SHOW_CURSOR = true;
}
show_cursor_if_no_touch = true;
break;
}
@@ -1410,12 +1460,7 @@ int main_implementation(int argc, char *argv[]) {
avs::game::DLL_NAME = "nostalgia.dll";
attach_io = true;
attach_nostalgia = true;
// automatically show cursor when no touchscreen is available
if (!is_touch_available()) {
GRAPHICS_SHOW_CURSOR = true;
}
show_cursor_if_no_touch = true;
break;
}
@@ -1521,12 +1566,7 @@ int main_implementation(int argc, char *argv[]) {
avs::game::DLL_NAME = "arknck.dll";
attach_io = true;
attach_we = true;
// automatically show cursor when no touchscreen is available
if (!is_touch_available()) {
GRAPHICS_SHOW_CURSOR = true;
}
show_cursor_if_no_touch = true;
break;
}
@@ -1559,10 +1599,7 @@ int main_implementation(int argc, char *argv[]) {
avs::game::DLL_NAME = "kamunity.dll";
attach_io = true;
attach_ccj = true;
// automatically show cursor when no touchscreen is available
if (!is_touch_available()) {
GRAPHICS_SHOW_CURSOR = true;
}
show_cursor_if_no_touch = true;
break;
}
@@ -1571,10 +1608,17 @@ int main_implementation(int argc, char *argv[]) {
avs::game::DLL_NAME = "kamunity.dll";
attach_io = true;
attach_qks = true;
// automatically show cursor when no touchscreen is available
if (!is_touch_available()) {
GRAPHICS_SHOW_CURSOR = true;
}
show_cursor_if_no_touch = true;
break;
}
// Mahjong Fight Girl
if (check_dll("kamunity.dll") && fileutils::dir_exists("game/MFGClient_Data")) {
avs::game::DLL_NAME = "kamunity.dll";
attach_io = true;
attach_mfg = true;
launcher::signal::USE_VEH_WORKAROUND = true;
show_cursor_if_no_touch = true;
break;
}
@@ -1583,10 +1627,7 @@ int main_implementation(int argc, char *argv[]) {
avs::game::DLL_NAME = "kamunity.dll";
attach_io = true;
attach_bc = true;
// automatically show cursor when no touchscreen is available
if (!is_touch_available()) {
GRAPHICS_SHOW_CURSOR = true;
}
show_cursor_if_no_touch = true;
break;
}
@@ -1729,6 +1770,10 @@ int main_implementation(int argc, char *argv[]) {
avs::core::set_default_heap_size("kamunity.dll");
games.push_back(new games::qks::QKSGame());
}
if (attach_mfg) {
avs::core::set_default_heap_size("kamunity.dll");
games.push_back(new games::mfg::MFGGame());
}
// apply user heap size, if defined
if (user_heap_size > 0) {
@@ -1761,6 +1806,9 @@ int main_implementation(int argc, char *argv[]) {
exit(spicecfg_run(sextet_devices));
}
// 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();
@@ -1778,6 +1826,11 @@ int main_implementation(int argc, char *argv[]) {
// print devices
RI_MGR->devices_print();
// 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;
}
// cardio
if (cardio_enabled) {
cardio_runner_start(true);
@@ -1923,6 +1976,8 @@ int main_implementation(int argc, char *argv[]) {
log_warning("launcher", "https://github.com/mon/ifs_layeredfs");
}
update_msvcrt_args(argc, argv);
// load hooks
for (auto &hook : game_hooks) {
log_info("launcher", "loading hook DLL {}", hook);
@@ -1964,6 +2019,11 @@ int main_implementation(int argc, char *argv[]) {
// start coin input thread
eamuse_coin_start_thread();
// pin macro
if (!cfg::CONFIGURATOR_STANDALONE && PIN_MACRO_ENABLED) {
eamuse_pin_macro_start_thread();
}
// print PEB
if (peb_print) {
peb::peb_print();
@@ -2048,6 +2108,8 @@ int main_implementation(int argc, char *argv[]) {
// stop coin input thread
eamuse_coin_stop_thread();
eamuse_pin_macro_stop_thread();
// BT5API
if (BT5API_ENABLED) {
bt5api_dispose();
@@ -2101,6 +2163,54 @@ int main_implementation(int argc, char *argv[]) {
return 0;
}
// https://github.com/spice2x/spice2x.github.io/issues/264
// huge ugly hack to work around things that broke when MinGW switched from msvcrt to ucrt
// this is done to ensure that any DLL hooks that rely on msvcrt continue to work
void update_msvcrt_args(int argc, char *argv[]) {
#if defined(_UCRT)
auto msvc = LoadLibraryA("msvcrt.dll");
if (!msvc) {
log_warning("launcher", "failed to load msvcrt.dll");
return;
}
// get __argc
PINT32 argc_addr = (PINT32)GetProcAddress(msvc, "__argc");
if (!argc_addr) {
log_warning("launcher", "failed to find msvcrt!__argc");
return;
}
try {
if (*argc_addr == argc) {
log_warning("launcher", "msvcrt!__argc is already set");
return;
}
} catch (const std::exception &e) {
log_warning("launcher", "exception while reading msvcrt!_argc: {}", e.what());
}
// get __argv
PCHAR **argv_addr = (PCHAR **)GetProcAddress(msvc, "__argv");
if (!argv_addr) {
log_warning("launcher", "failed to find msvcrt!__argv");
return;
}
// update them
try {
log_info("launcher", "msvcrt!__argc value before: {}", *argc_addr);
*argc_addr = argc;
log_info("launcher", "msvcrt!__argc value after: {}", *argc_addr);
*argv_addr = argv;
} catch (const std::exception &e) {
log_warning("launcher", "exception while messing with msvcrt!_argc and _argv: {}", e.what());
}
#else
log_misc("launcher", "not UCRT, skipping msvcrt!_argc / _argv hacks");
#endif
}
#ifndef SPICETOOLS_SPICECFG_STANDALONE
int main(int argc, char *argv[]) {
return main_implementation(argc, argv);
+122 -11
View File
@@ -19,6 +19,7 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
"Network",
"Overlay",
"Graphics (Common)",
"Graphics (Full Screen)",
"Graphics (Windowed)",
"Audio",
};
@@ -113,6 +114,26 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Network",
.sensitive = true,
},
{
// Player1PinMacro
.title = "Player 1 PIN Macro",
.name = "pinmacro0",
.desc = "Set a PIN for Player 1 that will cause the PIN to be automatically typed when Player 1 PIN Macro overlay key is pressed",
.type = OptionType::Text,
.setting_name = "1234",
.category = "Network (Advanced)",
.sensitive = true,
},
{
// Player2PinMacro
.title = "Player 2 PIN Macro",
.name = "pinmacro1",
.desc = "Set a PIN for Player 2 that will cause the PIN to be automatically typed when Player 2 PIN Macro overlay key is pressed",
.type = OptionType::Text,
.setting_name = "5678",
.category = "Network (Advanced)",
.sensitive = true,
},
{
.title = "Windowed Mode",
.name = "w",
@@ -165,9 +186,20 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
{
.title = "Monitor",
.name = "monitor",
.desc = "Sets the display that the game will be opened in, for multiple monitors",
.desc = "Sets the display that the game will be opened in, for multiple monitors.\n\n"
"0 is the primary monitor, 1 is the second monitor, and so on.\n\n"
"Not all games will respect this. Does not work at all with multi-monitor games like TDJ/UFC. "
"Disable Full Screen Optimizations for best results",
.type = OptionType::Integer,
.category = "Graphics (Common)",
.category = "Graphics (Full Screen)",
},
{
.title = "Only Use One Monitor",
.name = "graphics-force-single-adapter",
.desc = "Force the graphics device to be opened utilizing only one adapter in multi-monitor systems.\n\n"
"May cause unstable framerate and desyncs, especially if monitors have different refresh rates!",
.type = OptionType::Bool,
.category = "Graphics (Full Screen)",
},
{
.title = "Force Refresh Rate",
@@ -177,12 +209,29 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Graphics (Common)",
},
{
.title = "Only Use One Monitor",
.name = "graphics-force-single-adapter",
.desc = "Force the graphics device to be opened utilizing only one adapter in multi-monitor systems.\n\n"
"May cause unstable framerate and desyncs, especially if monitors have different refresh rates!",
// FullscreenResolution
.title = "Force Full Screen Resolution",
.name = "forceres",
.desc =
"For full screen mode, forcibly set a custom resolution.\n\n"
"Works great for some games, but can COMPLETELY BREAK other games - YMMV!\n\n"
"If you are using -forceresswap/-sdvxlandscape, put the TARGET monitor resolution in this field.\n\n"
"This should only be used as last resort if your GPU/monitor can't display the resolution required by the game",
.type = OptionType::Text,
.setting_name = "1280,720",
.category = "Graphics (Full Screen)"
},
{
// FullscreenOrientationFlip
.title = "Full Screen Orientation Swap",
.name = "forceresswap",
.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\n\n"
"WARNING: for SDVX, this messes with camera angle for note lanes, causing it to be zoomed out!",
.type = OptionType::Bool,
.category = "Graphics (Common)",
.category = "Graphics (Full Screen)"
},
{
// Graphics9On12
@@ -713,7 +762,13 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Enum,
.game_name = "Sound Voltex",
.category = "Overlay",
.elements = {{"top", ""}, {"center", ""}, {"bottom", ""}},
.elements = {
{"top", ""},
{"center", ""},
{"bottom", ""},
{"bottomleft", "for landscape"},
{"bottomright", "for landscape"},
},
},
{
// spice2x_SDVXSubRedraw
@@ -986,6 +1041,15 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "QuizKnock STADIUM",
.category = "Game Options (Advanced)",
},
{
// LoadMFGModule
.title = "Force Load Mahjong Fight Girl",
.name = "mfg",
.desc = "manually enable Mahjong Fight Girl module",
.type = OptionType::Bool,
.game_name = "Mahjong Fight Girl",
.category = "Game Options (Advanced)",
},
{
// LoadMusecaModule
.title = "Force Load Museca",
@@ -1024,7 +1088,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.title = "Screen Resize Config Path",
.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",
},
@@ -1033,7 +1097,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.title = "Patch Manager Config Path",
.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",
},
@@ -1651,6 +1715,39 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Chase Chase Jokers",
.category = "Game Options",
},
{
.title = "MFG Arguments Override",
.name = "mfgargs",
.desc = "Command line arguments passed to the game.",
.type = OptionType::Text,
.setting_name = "",
.game_name = "Mahjong Fight Girl",
.category = "Game Options (Advanced)",
},
{
.title = "MFG Cabinet Type",
.name = "mfgcabtype",
.desc = "MFG Cabinet Type. Default is HG.",
.type = OptionType::Enum,
.setting_name = "",
.game_name = "Mahjong Fight Girl",
.category = "Game Options",
.elements = {
{"HG", "HG"},
{"B", "B"},
{"C", "C"},
{"UKS", "UKS"},
},
},
{
.title = "MFG Disable IO Emulation",
.name = "mfgnoio",
.desc = "Disables BI2X hooks for MFG",
.type = OptionType::Bool,
.setting_name = "",
.game_name = "Mahjong Fight Girl",
.category = "Game Options (Advanced)"
},
{
// spice2x_LightsOverallBrightness
.title = "Lights Brightness Adjustment",
@@ -1721,7 +1818,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.title = "Window Forced Render Scaling",
.name = "windowscale",
.desc = "For windowed mode: forcibly set DX9 back buffer dimensions to match window size. "
"Works great on some games, but completely broken on others.",
"Reduces pixelated scaling artifacts. Works great for some games, but can COMPLETELY BREAK other games - YMMV!",
.type = OptionType::Bool,
.category = "Graphics (Windowed)",
},
@@ -1951,6 +2048,20 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Sound Voltex",
.category = "Game Options",
},
{
// SDVXFullscreenLandscape
.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",
},
{
// spice2x_EnableSMXStage
.title = "StepManiaX Stage Lighting Support",
+10 -1
View File
@@ -18,6 +18,8 @@ namespace launcher {
PCBID,
Player1Card,
Player2Card,
Player1PinMacro,
Player2PinMacro,
WindowedMode,
InjectHook,
EarlyInjectHook,
@@ -25,8 +27,10 @@ namespace launcher {
CaptureCursor,
ShowCursor,
DisplayAdapter,
GraphicsForceRefresh,
GraphicsForceSingleAdapter,
GraphicsForceRefresh,
FullscreenResolution,
FullscreenOrientationFlip,
Graphics9On12,
spice2x_Dx9On12,
NoLegacy,
@@ -112,6 +116,7 @@ namespace launcher {
LoadBusouShinkiModule,
LoadCCJModule,
LoadQKSModule,
LoadMFGModule,
LoadMusecaModule,
PathToModules,
ScreenshotFolder,
@@ -191,6 +196,9 @@ namespace launcher {
CCJMouseTrackball,
CCJMouseTrackballWithToggle,
CCJTrackballSensitivity,
MFGArgs,
MFGCabType,
MFGNoIO,
spice2x_LightsOverallBrightness,
spice2x_WindowBorder,
spice2x_WindowSize,
@@ -215,6 +223,7 @@ namespace launcher {
spice2x_NoNVAPI,
spice2x_NoD3D9DeviceHook,
spice2x_SDVXNoSub,
SDVXFullscreenLandscape,
spice2x_EnableSMXStage,
spice2x_EnableSMXDedicab,
IIDXRecQuality,
+2 -1
View File
@@ -27,6 +27,7 @@ namespace launcher::signal {
// settings
bool DISABLE = false;
bool USE_VEH_WORKAROUND = false;
}
#define V(variant) case variant: return #variant
@@ -167,7 +168,7 @@ static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter_hook(
static PVOID WINAPI AddVectoredExceptionHandler_hook(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler) {
log_info("signal", "AddVectoredExceptionHandler hook hit");
return nullptr;
return launcher::signal::USE_VEH_WORKAROUND ? INVALID_HANDLE_VALUE : nullptr;
}
void launcher::signal::attach() {
+1
View File
@@ -4,6 +4,7 @@ namespace launcher::signal {
// settings
extern bool DISABLE;
extern bool USE_VEH_WORKAROUND;
//void print_stacktrace();
void attach();
+117 -13
View File
@@ -11,6 +11,7 @@
#include "util/logging.h"
#include "util/time.h"
#include "util/utils.h"
#include "overlay/overlay.h"
#include "bt5api.h"
@@ -39,6 +40,16 @@ static std::optional<double> AUTO_INSERT_CARD_FIRST_CONSUME_TIME;
static bool AUTO_INSERT_CARD_CACHED[2];
static uint8_t AUTO_INSERT_CARD_CACHED_DATA[2][8];
// pin macro
bool PIN_MACRO_ENABLED = false;
std::string PIN_MACRO_VALUES[2] = {"", ""};
static std::thread *PIN_MACRO_THREAD = nullptr;
static bool PIN_MACRO_THREAD_ACTIVE = false;
static uint16_t PIN_MACRO_TRIGGER_KEYS[2] = {
games::OverlayButtons::TriggerPinMacroP1,
games::OverlayButtons::TriggerPinMacroP2
};
bool eamuse_get_card(int active_count, int unit_id, uint8_t *card) {
// get unit index
@@ -55,7 +66,7 @@ bool eamuse_get_card(int active_count, int unit_id, uint8_t *card) {
if (CARD_INSERT_UID_ENABLE[index]) {
CARD_INSERT_UID_ENABLE[index] = false;
memcpy(card, CARD_INSERT_UID[index], 8);
log_info("eamuse", "Inserted card from reader {}: {}", index, bin2hex(card, 8));
log_info("eamuse", "[P{}] Inserted card from reader: {}", index+1, bin2hex(card, 8));
return true;
}
@@ -72,35 +83,40 @@ bool eamuse_get_card(int active_count, int unit_id, uint8_t *card) {
}
bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int index) {
// do a quick copy under lock
std::unique_lock<std::mutex> lock(CARD_OVERRIDES_LOCK);
const auto card_override = CARD_OVERRIDES[index];
lock.unlock();
// Check if card overrides are present
if (!CARD_OVERRIDES[index].empty()) {
if (!card_override.empty()) {
// Override is present
for (int n = 0; n < 16; n++) {
char c = CARD_OVERRIDES[index].c_str()[n];
char c = card_override.c_str()[n];
bool digit = c >= '0' && c <= '9';
bool character_big = c >= 'A' && c <= 'F';
bool character_small = c >= 'a' && c <= 'f';
if (!digit && !character_big && !character_small) {
log_warning("eamuse",
"{} card override contains an invalid character sequence at byte {} (16 characters, 0-9/A-F only)",
CARD_OVERRIDES[index], n);
card_override, n);
return false;
}
}
// Log info
log_info("eamuse", "Inserted card override: {}", CARD_OVERRIDES[index]);
log_info("eamuse", "[P{}] Inserted card override: {}", index+1, card_override);
// Card is valid, convert and set it.
hex2bin(CARD_OVERRIDES[index].c_str(), card);
hex2bin(card_override.c_str(), card);
// cache it for auto-insert
if (AUTO_INSERT_CARD[index] && !AUTO_INSERT_CARD_CACHED[index]) {
// always overwrite from overrides since override may have changed by user in card manager
if (AUTO_INSERT_CARD[index]) {
memcpy(AUTO_INSERT_CARD_CACHED_DATA[index], card, 8);
AUTO_INSERT_CARD_CACHED[index] = true;
log_info("eamuse", "Auto card insert - caching this card in memory: {}", CARD_OVERRIDES[index]);
log_info("eamuse", "[P{}] Auto card insert - caching this card override in memory: {}", index+1, card_override);
}
// success
@@ -151,7 +167,7 @@ bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card,
}
// info
log_info("eamuse", "Inserted {}: {}", path.string(), buffer);
log_info("eamuse", "[P{}] Inserted {}: {}", index+1, path.string(), buffer);
// convert hex to bytes
hex2bin(buffer, card);
@@ -160,7 +176,7 @@ bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card,
if (AUTO_INSERT_CARD[index] && !AUTO_INSERT_CARD_CACHED[index]) {
memcpy(AUTO_INSERT_CARD_CACHED_DATA[index], card, 8);
AUTO_INSERT_CARD_CACHED[index] = true;
log_info("eamuse", "Auto card insert - caching this card in memory: {}", buffer);
log_info("eamuse", "[P{}] Auto card insert - caching this card from file in memory: {}", index+1, buffer);
}
// success
@@ -200,7 +216,6 @@ bool eamuse_card_insert_consume(int active_count, int unit_id) {
if (!CARD_INSERT[index]) {
eamuse_card_insert(index);
// not logging anything here to prevent spam
// log_info("eamuse", "Automatic card insert on {}/{} (-autocard)", unit_id + 1, active_count);
return true;
} else {
return false;
@@ -212,7 +227,7 @@ bool eamuse_card_insert_consume(int active_count, int unit_id) {
auto offset = unit_id * games::KeypadButtons::Size;
if ((CARD_INSERT[index] && fabs(get_performance_seconds() - CARD_INSERT_TIME[index]) < CARD_INSERT_TIMEOUT)
|| GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::InsertCard + offset))) {
log_info("eamuse", "Card insert on {}/{}", unit_id + 1, active_count);
log_info("eamuse", "[P{}] Card insert on reader (total active count: {})", unit_id+1, active_count);
CARD_INSERT[index] = false;
return true;
}
@@ -295,6 +310,93 @@ void eamuse_coin_stop_thread() {
COIN_INPUT_THREAD = nullptr;
}
void eamuse_pin_macro_start_thread() {
// set active
PIN_MACRO_THREAD_ACTIVE = true;
// create thread
PIN_MACRO_THREAD = new std::thread([]() {
uint16_t keypad_overrides[] = {
1 << EAM_IO_KEYPAD_0,
1 << EAM_IO_KEYPAD_1,
1 << EAM_IO_KEYPAD_2,
1 << EAM_IO_KEYPAD_3,
1 << EAM_IO_KEYPAD_4,
1 << EAM_IO_KEYPAD_5,
1 << EAM_IO_KEYPAD_6,
1 << EAM_IO_KEYPAD_7,
1 << EAM_IO_KEYPAD_8,
1 << EAM_IO_KEYPAD_9,
};
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
size_t pin_index[2] = {PIN_MACRO_VALUES[0].length(), PIN_MACRO_VALUES[1].length()};
std::optional<uint8_t> active_unit = std::nullopt;
while (PIN_MACRO_THREAD_ACTIVE) {
// wait for key press
if (!active_unit.has_value()) {
for (int unit = 0; unit < 2; unit++) {
if (PIN_MACRO_VALUES[unit].empty()) {
continue;
}
if (overlay_buttons &&
(!overlay::OVERLAY || overlay::OVERLAY->hotkeys_triggered()) &&
GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(PIN_MACRO_TRIGGER_KEYS[unit]))) {
active_unit = unit;
// Reset key index
pin_index[unit] = 0;
break;
}
}
if (!active_unit.has_value()) {
Sleep(20);
continue;
}
}
const auto unit = active_unit.value();
// get character from config
if (pin_index[unit] < PIN_MACRO_VALUES[unit].length()) {
// insert character
char pin_char = PIN_MACRO_VALUES[unit].at(pin_index[unit]);
if (pin_char >= '0' && pin_char <= '9') {
int char_index = pin_char - '0';
eamuse_set_keypad_overrides(unit, keypad_overrides[char_index]);
}
pin_index[unit]++;
Sleep(100);
// clear
eamuse_set_keypad_overrides(unit, 0);
Sleep(50);
// end of PIN
if (pin_index[unit] == PIN_MACRO_VALUES[unit].length()) {
active_unit = std::nullopt;
Sleep(120);
}
continue;
}
Sleep(200);
}
});
}
void eamuse_pin_macro_stop_thread() {
PIN_MACRO_THREAD_ACTIVE = false;
if (PIN_MACRO_THREAD != nullptr) {
PIN_MACRO_THREAD->join();
delete PIN_MACRO_THREAD;
PIN_MACRO_THREAD = nullptr;
}
}
void eamuse_set_keypad_overrides(size_t unit, uint16_t keypad_state) {
// check unit
@@ -555,6 +657,8 @@ void eamuse_autodetect_game() {
eamuse_set_game("Chase Chase Jokers");
else if (avs::game::is_model("UKS"))
eamuse_set_game("QuizKnock STADIUM");
else if (avs::game::is_model("VFG"))
eamuse_set_game("Mahjong Fight Girl");
else {
log_warning("eamuse", "unknown game model: {}", avs::game::MODEL);
eamuse_set_game("unknown");
+8
View File
@@ -4,6 +4,7 @@
#include <filesystem>
#include <string>
#include <optional>
#include <mutex>
#include "external/scard/scard.h"
@@ -30,10 +31,14 @@ enum eam_io_keypad_scan_code {
EAM_IO_INSERT = 13, /* SpiceTools Extension */
};
extern std::mutex CARD_OVERRIDES_LOCK;
extern std::string CARD_OVERRIDES[2];
extern bool AUTO_INSERT_CARD[2];
extern float AUTO_INSERT_CARD_COOLDOWN;
extern bool PIN_MACRO_ENABLED;
extern std::string PIN_MACRO_VALUES[2];
bool eamuse_get_card(int active_count, int unit_id, uint8_t *card);
bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int unit_id);
bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card, int index);
@@ -56,6 +61,9 @@ int eamuse_coin_add();
void eamuse_coin_start_thread();
void eamuse_coin_stop_thread();
void eamuse_pin_macro_start_thread();
void eamuse_pin_macro_stop_thread();
void eamuse_set_keypad_overrides(size_t unit, uint16_t keypad_state);
void eamuse_set_keypad_overrides_bt5(size_t unit, uint16_t keypad_state);
void eamuse_set_keypad_overrides_reader(size_t unit, uint16_t keypad_state);
+1 -1
View File
@@ -36,7 +36,7 @@ namespace wintouchemu {
bool LOG_FPS = false;
static inline bool is_emu_enabled() {
return FORCE || !is_touch_available() || GRAPHICS_SHOW_CURSOR;
return FORCE || !is_touch_available("wintouchemu::is_emu_enabled") || GRAPHICS_SHOW_CURSOR;
}
static decltype(GetSystemMetrics) *GetSystemMetrics_orig = nullptr;
+44 -19
View File
@@ -6,36 +6,61 @@
namespace ImGui {
const auto fg = ImVec4(0.910f, 0.914f, 0.922f, 1.0f);
const auto bg = ImVec4(0.192f, 0.212f, 0.220f, 1.0f);
void HelpTooltip(const char* desc) {
ImGui::PushStyleColor(ImGuiCol_Border, bg);
ImGui::PushStyleColor(ImGuiCol_BorderShadow, bg);
ImGui::PushStyleColor(ImGuiCol_PopupBg, bg);
ImGui::PushStyleColor(ImGuiCol_Text, fg);
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(desc);
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
ImGui::PopStyleColor(4);
}
void HelpMarker(const char* desc) {
ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(desc);
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
HelpTooltip(desc);
}
}
void WarnTooltip(const char* desc, const char* warn) {
ImGui::PushStyleColor(ImGuiCol_Border, bg);
ImGui::PushStyleColor(ImGuiCol_BorderShadow, bg);
ImGui::PushStyleColor(ImGuiCol_PopupBg, bg);
ImGui::PushStyleColor(ImGuiCol_Text, fg);
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
if (desc && desc[0]) {
ImGui::TextUnformatted(desc);
ImGui::TextUnformatted("");
}
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 0.f, 1.f));
if (warn && warn[0]) {
ImGui::TextUnformatted("WARNING:");
ImGui::TextUnformatted(warn);
}
ImGui::PopStyleColor();
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
ImGui::PopStyleColor(4);
}
void WarnMarker(const char* desc, const char* warn) {
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImVec4(1.f, 1.f, 0.f, 1.f));
ImGui::TextDisabled("(!)");
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
if (desc) {
ImGui::TextUnformatted(desc);
ImGui::TextUnformatted("");
}
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 0.f, 1.f));
if (warn) {
ImGui::TextUnformatted("WARNING:");
ImGui::TextUnformatted(warn);
}
ImGui::PopStyleColor();
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
WarnTooltip(desc, warn);
}
}
+2
View File
@@ -2,7 +2,9 @@
namespace ImGui {
void HelpTooltip(const char* desc);
void HelpMarker(const char* desc);
void WarnTooltip(const char* desc, const char* warn);
void WarnMarker(const char* desc, const char* warn);
void DummyMarker();
void Knob(float fraction, float size, float thickness = 2.f,
+35 -27
View File
@@ -2,6 +2,7 @@
#include <windows.h>
#include "cfg/configurator.h"
#include "games/io.h"
#include "launcher/launcher.h"
#include "launcher/superexit.h"
@@ -12,6 +13,16 @@
#include "util/logging.h"
#include "util/utils.h"
#if !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) || \
!defined(IMGUI_DISABLE_DEFAULT_ALLOCATORS) || \
!defined(IMGUI_USE_BGRA_PACKED_COLOR) || \
!defined(IMGUI_HAS_VIEWPORT) || \
!defined(IMGUI_HAS_DOCK) || \
!defined(IMGUI_DISABLE_DEMO_WINDOWS) || \
defined(IMGUI_DISABLE_DEBUG_TOOLS)
#error "fix imconfig.h after updating imgui version"
#endif
// state
static HWND g_hWnd = nullptr;
static INT64 g_Time = 0;
@@ -42,6 +53,7 @@ bool ImGui_ImplSpice_Init(HWND hWnd) {
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;
io.BackendPlatformName = "imgui_impl_spice";
io.ConfigErrorRecoveryEnableTooltip = true;
// keyboard mapping
io.KeyMap[ImGuiKey_Tab] = VK_TAB;
@@ -59,7 +71,7 @@ bool ImGui_ImplSpice_Init(HWND hWnd) {
io.KeyMap[ImGuiKey_Space] = VK_SPACE;
io.KeyMap[ImGuiKey_Enter] = VK_RETURN;
io.KeyMap[ImGuiKey_Escape] = VK_ESCAPE;
io.KeyMap[ImGuiKey_KeyPadEnter] = VK_RETURN;
io.KeyMap[ImGuiKey_KeypadEnter] = VK_RETURN;
io.KeyMap[ImGuiKey_A] = 'A';
io.KeyMap[ImGuiKey_C] = 'C';
io.KeyMap[ImGuiKey_V] = 'V';
@@ -349,27 +361,6 @@ void ImGui_ImplSpice_NewFrame() {
}
}
// navigator input
auto buttons = games::get_buttons_overlay(eamuse_get_game());
if (buttons && (!overlay::OVERLAY || overlay::OVERLAY->hotkeys_triggered())) {
struct {
size_t index;
Button &btn;
} NAV_MAPPING[] = {
{ ImGuiNavInput_Activate, buttons->at(games::OverlayButtons::NavigatorActivate )},
{ ImGuiNavInput_Cancel, buttons->at(games::OverlayButtons::NavigatorCancel) },
{ ImGuiNavInput_DpadUp, buttons->at(games::OverlayButtons::NavigatorUp) },
{ ImGuiNavInput_DpadDown, buttons->at(games::OverlayButtons::NavigatorDown) },
{ ImGuiNavInput_DpadLeft, buttons->at(games::OverlayButtons::NavigatorLeft) },
{ ImGuiNavInput_DpadRight, buttons->at(games::OverlayButtons::NavigatorRight) },
};
for (auto mapping : NAV_MAPPING) {
if (GameAPI::Buttons::getState(RI_MGR, mapping.btn)) {
io.NavInputs[mapping.index] = 1;
}
}
}
// set mouse wheel
auto mouse_diff = mouse_wheel - mouse_wheel_last;
mouse_wheel_last = mouse_wheel;
@@ -378,10 +369,27 @@ void ImGui_ImplSpice_NewFrame() {
// update OS mouse position
ImGui_ImplSpice_UpdateMousePos();
// update OS mouse cursor with the cursor requested by imgui
ImGuiMouseCursor mouse_cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor();
if (g_LastMouseCursor != mouse_cursor) {
g_LastMouseCursor = mouse_cursor;
ImGui_ImplSpice_UpdateMouseCursor();
if (cfg::CONFIGURATOR_STANDALONE) {
// if cursor is inside the client area, always set the OS cursor to what ImGui wants
// this is to deal with cases where mouse cursor changes outside the client rect and comes
// back into the window
// i'm sure there might be better ways to deal with this but this works so whatever, right?
RECT client_rect;
if (GetClientRect(g_hWnd, &client_rect)) {
POINT cursor;
if (GetCursorPos(&cursor) && ScreenToClient(g_hWnd, &cursor)) {
if (client_rect.left < cursor.x && cursor.x < client_rect.right &&
client_rect.top < cursor.y && cursor.y < client_rect.bottom) {
ImGui_ImplSpice_UpdateMouseCursor();
}
}
}
} else {
// update OS mouse cursor with the cursor requested by imgui
ImGuiMouseCursor mouse_cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor();
if (g_LastMouseCursor != mouse_cursor) {
g_LastMouseCursor = mouse_cursor;
ImGui_ImplSpice_UpdateMouseCursor();
}
}
}
+1 -1
View File
@@ -678,7 +678,7 @@ void bind_imgui_painting()
int font_width, font_height;
io.Fonts->GetTexDataAsAlpha8(&tex_data, &font_width, &font_height);
const auto texture = new Texture{tex_data, font_width, font_height};
io.Fonts->TexID = texture;
io.Fonts->TexID = reinterpret_cast<ImTextureID>(texture);
}
static Stats s_stats; // TODO: pass as an argument?
+44 -22
View File
@@ -7,14 +7,12 @@
#include "hooks/graphics/graphics.h"
#include "misc/eamuse.h"
#include "touch/touch.h"
#include "util/fileutils.h"
#include "util/logging.h"
#include "util/resutils.h"
#include "build/resource.h"
#include "imgui/impl_dx9.h"
#include "imgui/impl_spice.h"
#include "imgui/impl_sw.h"
#include "overlay/imgui/impl_dx9.h"
#include "external/imgui/backends/imgui_impl_dx9.h"
#include "overlay/imgui/impl_spice.h"
#include "overlay/imgui/impl_sw.h"
@@ -62,6 +60,7 @@ namespace overlay {
std::mutex OVERLAY_MUTEX;
std::unique_ptr<overlay::SpiceOverlay> OVERLAY = nullptr;
ImFont* DSEG_FONT = nullptr;
bool SHOW_DEBUG_LOG_WINDOW = false;
}
static void *ImGui_Alloc(size_t sz, void *user_data) {
@@ -242,13 +241,20 @@ void overlay::SpiceOverlay::init() {
io.UserData = this;
io.ConfigFlags = ImGuiConfigFlags_NavEnableKeyboard
| ImGuiConfigFlags_NavEnableGamepad
| ImGuiConfigFlags_NavEnableSetMousePos
| ImGuiConfigFlags_DockingEnable
| ImGuiConfigFlags_ViewportsEnable;
if (is_touch_available()) {
| ImGuiConfigFlags_NavEnableSetMousePos;
if (!cfg::CONFIGURATOR_STANDALONE) {
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
}
if (is_touch_available("SpiceOverlay::init")) {
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
}
// temporarily turn this off as it can cause crashes during font load failures
// turns back on in ImGui_ImplSpice_Init
io.ConfigErrorRecoveryEnableTooltip = false;
io.MouseDrawCursor = !GRAPHICS_SHOW_CURSOR;
// disable config
@@ -263,20 +269,14 @@ void overlay::SpiceOverlay::init() {
// add fallback fonts for missing glyph ranges
ImFontConfig config {};
config.MergeMode = true;
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\simsun.ttc)",
13.0f, &config, io.Fonts->GetGlyphRangesChineseSimplifiedCommon());
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\arial.ttf)",
13.0f, &config, io.Fonts->GetGlyphRangesCyrillic());
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\meiryu.ttc)",
13.0f, &config, io.Fonts->GetGlyphRangesJapanese());
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\meiryo.ttc)",
13.0f, &config, io.Fonts->GetGlyphRangesJapanese());
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\gulim.ttc)",
13.0f, &config, io.Fonts->GetGlyphRangesKorean());
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\cordia.ttf)",
13.0f, &config, io.Fonts->GetGlyphRangesThai());
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\arial.ttf)",
13.0f, &config, io.Fonts->GetGlyphRangesVietnamese());
add_font("simsun.ttc", &config, io.Fonts->GetGlyphRangesChineseSimplifiedCommon());
add_font("arial.ttc", &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 special font
if (avs::game::is_model("LDJ")) {
@@ -458,6 +458,10 @@ void overlay::SpiceOverlay::new_frame() {
window->build();
}
if (SHOW_DEBUG_LOG_WINDOW) {
ImGui::ShowDebugLogWindow(&SHOW_DEBUG_LOG_WINDOW);
}
// end frame
ImGui::EndFrame();
}
@@ -693,3 +697,21 @@ uint32_t *overlay::SpiceOverlay::sw_get_pixel_data(int *width, int *height) {
*height = this->pixel_data_height;
return &this->pixel_data[0];
}
void overlay::SpiceOverlay::add_font(const char* font, ImFontConfig* config, const ImWchar* glyphs) {
CHAR fonts_dir[MAX_PATH];
ExpandEnvironmentStringsA(R"(%SYSTEMROOT%\Fonts\)", fonts_dir, MAX_PATH);
std::filesystem::path full_path = fonts_dir;
full_path += font;
if (fileutils::file_exists(full_path)) {
log_misc("overlay", "loading font: {}", full_path.string());
ImGui::GetIO().Fonts->AddFontFromFileTTF(
full_path.string().c_str(),
13.0f,
config,
glyphs);
} else {
log_misc("overlay", "font not found: {}", full_path.string());
}
}
+2
View File
@@ -26,6 +26,7 @@ namespace overlay {
extern bool AUTO_SHOW_KEYPAD_P2;
extern bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT;
extern bool FPS_SHOULD_FLIP;
extern bool SHOW_DEBUG_LOG_WINDOW;
class SpiceOverlay {
public:
@@ -128,6 +129,7 @@ namespace overlay {
bool hotkey_toggle_last = false;
void init();
void add_font(const char* font, ImFontConfig* config, const ImWchar* glyphs);
};
// global
+2 -3
View File
@@ -99,10 +99,9 @@ void overlay::Window::build() {
for (auto &child : this->children) {
child->build();
}
// end window
ImGui::End();
}
// end window
ImGui::End();
if (this->remove_window_padding) {
ImGui::PopStyleVar();
+1 -1
View File
@@ -3,7 +3,7 @@
#include <string>
#include "external/imgui/imgui.h"
#include "external/imgui/imgui_stdlib.h"
#include "external/imgui/misc/cpp/imgui_stdlib.h"
#include "overlay.h"
namespace overlay {
+208 -100
View File
@@ -5,9 +5,11 @@
#include "external/rapidjson/document.h"
#include "external/rapidjson/prettywriter.h"
#include "misc/eamuse.h"
#include "misc/clipboard.h"
#include "util/utils.h"
#include "util/fileutils.h"
#include "cfg/configurator.h"
#include "overlay/imgui/extensions.h"
using namespace rapidjson;
@@ -27,19 +29,76 @@ 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();
}
// load -card0 / -card1
// -card0 / -card1 override
{
std::lock_guard<std::mutex> lock(CARD_OVERRIDES_LOCK);
if (!CARD_OVERRIDES[0].empty()) {
const CardEntry card0 = {
.name = "P1 Default (-card0)",
.id = CARD_OVERRIDES[0],
.search_string = "p1 default (-card0)",
.read_only = true,
.color = {0.9f, 0.9f, 0.9f}
};
card_cmd_overrides[0].emplace(card0);
this->loaded_card[0] = card0;
}
if (eamuse_get_game_keypads() > 1 && !CARD_OVERRIDES[1].empty()) {
const CardEntry card1 = {
.name = "P2 Default (-card1)",
.id = CARD_OVERRIDES[1],
.search_string = "p2 default (-card1)",
.read_only = true,
.color = {0.9f, 0.9f, 0.9f}
};
card_cmd_overrides[1].emplace(card1);
this->loaded_card[1] = card1;
}
}
}
CardManager::~CardManager() {
}
void CardManager::build_content() {
ImGui::SeparatorText("Selected card");
build_card();
ImGui::SeparatorText("Available cards");
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Active card overrides");
ImGui::SameLine();
ImGui::HelpMarker(
"Click to insert card now, or press Insert Card key. Auto Card Insert will also use these cards.\n\n"
"If no override is set, pressing Insert Card will read from card0.txt / card1.txt.");
if (ImGui::BeginTable("CardSetTable", eamuse_get_game_keypads() > 1 ? 2 : 1, ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted("Player 1");
if (eamuse_get_game_keypads() > 1) {
ImGui::TableNextColumn();
ImGui::TextUnformatted("Player 2");
}
ImGui::TableNextRow();
for (size_t i = 0; i < 2; i++) {
if (eamuse_get_game_keypads() > (int)i) {
ImGui::TableNextColumn();
if (build_card(i) && this->loaded_card[i].has_value()) {
insert_card_over_api(i, this->loaded_card[i].value());
}
}
}
ImGui::EndTable();
}
ImGui::Spacing();
ImGui::Spacing();
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Available cards");
build_card_list();
ImGui::Separator();
ImGui::Spacing();
@@ -48,44 +107,13 @@ namespace overlay::windows {
build_card_editor();
}
void CardManager::build_card() {
ImGui::BeginDisabled(this->current_card == nullptr);
// insert P1 button
if (ImGui::Button("Insert P1")) {
const auto card = this->current_card;
uint8_t card_bin[8];
if (card && card->id.length() == 16 && hex2bin(card->id.c_str(), card_bin)) {
eamuse_card_insert(0, card_bin);
}
}
// insert P2 button
if (eamuse_get_game_keypads() > 1) {
ImGui::SameLine();
if (ImGui::Button("Insert P2")) {
const auto card = this->current_card;
uint8_t card_bin[8];
if (card && card->id.length() == 16 && hex2bin(card->id.c_str(), card_bin)) {
eamuse_card_insert(1, card_bin);
}
}
}
// edit selected card
ImGui::SameLine();
if (ImGui::Button("Edit Card")) {
open_card_editor();
}
ImGui::EndDisabled();
ImGui::Spacing();
// card ui
if (this->current_card) {
const auto card = this->current_card;
const ImVec4 color(card->color[0], card->color[1], card->color[2], 1.f);
float bg_luminance = (0.299f * card->color[0] + 0.587 * card->color[1] + 0.114 * card->color[2]);
bool CardManager::build_card(int reader) {
ImGui::PushID(reader);
bool clicked = false;
if (this->loaded_card[reader].has_value()) {
const auto &card = this->loaded_card[reader].value();
const ImVec4 color(card.color[0], card.color[1], card.color[2], 1.f);
float bg_luminance = (0.299f * card.color[0] + 0.587 * card.color[1] + 0.114 * card.color[2]);
// text color
ImVec4 text_color;
@@ -101,22 +129,25 @@ namespace overlay::windows {
ImGui::PushStyleColor(ImGuiCol_Text, text_color);
if (ImGui::Button(fmt::format(
" {} \n {} {} {} {} ",
card->name.empty() ? "<blank>" : card->name.substr(0, 19),
card->id.substr(0, 4).c_str(),
card->id.substr(4, 4).c_str(),
card->id.substr(8, 4).c_str(),
card->id.substr(12, 4).c_str()
card.name.empty() ? "<blank>" : card.name.substr(0, 19),
card.id.substr(0, 4).c_str(),
card.id.substr(4, 4).c_str(),
card.id.substr(8, 4).c_str(),
card.id.substr(12, 4).c_str()
).c_str())) {
open_card_editor();
clicked = true;
}
ImGui::PopStyleColor(4);
} else {
ImGui::BeginDisabled();
ImGui::Button(" <No card> \n xxxx xxxx xxxx xxxx ");
ImGui::Button(" (No override set) \n"
" xxxx xxxx xxxx xxxx ");
ImGui::EndDisabled();
ImGui::PopStyleColor(4);
}
ImGui::PopID();
return clicked;
}
void CardManager::open_card_editor() {
@@ -140,13 +171,18 @@ namespace overlay::windows {
this->card_buffer,
std::size(this->card_buffer),
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase);
ImGui::EndDisabled();
if (this->current_card == nullptr) {
ImGui::SameLine();
if (ImGui::Button("Generate")) {
generate_ea_card(this->card_buffer);
}
} else {
ImGui::SameLine();
if (ImGui::Button("Copy")) {
clipboard::copy_text(this->current_card->id);
}
}
ImGui::EndDisabled();
// name field
ImGui::InputTextWithHint("Card Name", "Main Card",
@@ -171,6 +207,18 @@ namespace overlay::windows {
this->current_card->color[1] = this->color_buffer[1];
this->current_card->color[2] = this->color_buffer[2];
generate_search_string(this->current_card);
// ensure loaded cards are kept up to date
// note: does not handle cases where multiple cards have the same ID
for (size_t i = 0; i < 2; i++) {
if (this->loaded_card[i].has_value() &&
!this->loaded_card[i].value().read_only &&
this->loaded_card[i].value().id == this->current_card->id) {
this->loaded_card[i] = *this->current_card;
break;
}
}
} else {
// create a new card
CardEntry card {
@@ -211,14 +259,54 @@ namespace overlay::windows {
}
}
void CardManager::build_card_selectable(CardEntry &card) {
// generate card name
std::string card_name = "";
if (card.id.length() == 16) {
card_name += card.id.substr(0, 4);
card_name += " ";
card_name += card.id.substr(4, 4);
card_name += " ";
card_name += card.id.substr(8, 4);
card_name += " ";
card_name += card.id.substr(12, 4);
} else {
card_name += card.id;
}
if (!card.name.empty()) {
card_name += " - ";
card_name += card.name;
}
ImGui::PushID(&card);
// color button
ImVec4 color(card.color[0], card.color[1], card.color[2], 1.f);
ImGui::PushStyleColor(ImGuiCol_Button, color);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, color);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, color);
ImGui::SmallButton(" ");
ImGui::PopStyleColor(3);
ImGui::SameLine();
// selectable item
if (ImGui::Selectable(card_name.c_str(), this->current_card == &card)) {
this->current_card = &card;
}
ImGui::PopID();
}
void CardManager::build_card_list() {
// search for card
//
// setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below
// to return true, making it necessary to provide a callback...
ImGui::SetNextItemWidth(220);
ImGui::SetNextItemWidth(240);
if (ImGui::InputTextWithHint("", "Type here to search..", &this->search_filter)) {
this->current_card = nullptr;
this->search_filter_in_lower_case = strtolower(this->search_filter);
}
if (!this->search_filter.empty()) {
@@ -227,11 +315,49 @@ namespace overlay::windows {
this->search_filter.clear();
this->search_filter_in_lower_case.clear();
}
} else {
ImGui::SameLine();
// move selected up/down the list
ImGui::BeginDisabled(this->current_card == nullptr);
}
// toolbar
// set card as p1/p2
for (size_t i = 0; i < 2; i++) {
if (eamuse_get_game_keypads() > (int)i) {
if (i != 0) {
ImGui::SameLine();
}
ImGui::PushID(i);
ImGui::BeginDisabled(this->current_card == nullptr);
if (ImGui::Button(i == 0 ? "Load P1" : "Load P2") && this->current_card) {
this->loaded_card[i] = *this->current_card;
log_info(
"cardmanager",
"[P{}] update override and insert card: {} ({})",
i+1,
this->current_card->id,
this->current_card->name
);
// update override
std::lock_guard<std::mutex> lock(CARD_OVERRIDES_LOCK);
CARD_OVERRIDES[i] = this->current_card->id;
// insert card over api
insert_card_over_api(i, this->loaded_card[i].value());
}
ImGui::EndDisabled();
ImGui::PopID();
}
}
// edit selected card
ImGui::SameLine();
ImGui::BeginDisabled(this->current_card == nullptr || this->current_card->read_only);
if (ImGui::Button("Edit")) {
open_card_editor();
}
// move selected up/down the list
if (this->search_filter.empty()) {
ImGui::SameLine();
if (ImGui::Button("Move Up")) {
for (auto it = this->cards.begin(); it != this->cards.end(); ++it) {
@@ -254,60 +380,37 @@ namespace overlay::windows {
}
}
}
ImGui::EndDisabled();
}
ImGui::EndDisabled();
ImGui::Spacing();
// cards list
// use all available vertical space, minus height of buttons, minus separator
// use all available vertical space, minus height footer (a row of buttons and separator)
if (ImGui::BeginChild(
"cards",
ImVec2(0, ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing() - 8.f))) {
// -card0 / -card1 override
for (size_t i = 0; i < 2; i++) {
if (card_cmd_overrides[i].has_value()) {
build_card_selectable(card_cmd_overrides[i].value());
}
}
// cards from card manager JSON
for (auto &card : this->cards) {
// get card name
std::string card_name = "";
if (card.id.length() == 16) {
card_name += card.id.substr(0, 4);
card_name += " ";
card_name += card.id.substr(4, 4);
card_name += " ";
card_name += card.id.substr(8, 4);
card_name += " ";
card_name += card.id.substr(12, 4);
} else {
card_name += card.id;
}
if (!card.name.empty()) {
card_name += " - ";
card_name += card.name;
}
if (!this->search_filter_in_lower_case.empty() && !card.search_string.empty()) {
const bool matched =
card.search_string.find(this->search_filter_in_lower_case) != std::string::npos;
card.search_string.find(this->search_filter_in_lower_case) != std::string::npos;
if (!matched) {
continue;
}
}
// draw entry
ImGui::PushID(&card);
ImVec4 color(card.color[0], card.color[1], card.color[2], 1.f);
ImGui::PushStyleColor(ImGuiCol_Button, color);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, color);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, color);
ImGui::SmallButton(" ");
ImGui::PopStyleColor(3);
ImGui::SameLine();
if (ImGui::Selectable(card_name.c_str(), this->current_card == &card)) {
this->current_card = &card;
}
ImGui::PopID();
build_card_selectable(card);
}
}
ImGui::EndChild();
@@ -334,8 +437,6 @@ namespace overlay::windows {
}
void CardManager::config_load() {
log_info("cardmanager", "loading config");
// clear cards
this->cards.clear();
@@ -466,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");
}
}
@@ -504,4 +605,11 @@ namespace overlay::windows {
this->color_buffer[1] = g / 255.f;
this->color_buffer[2] = b / 255.f;
}
void CardManager::insert_card_over_api(int reader, CardEntry &card) {
uint8_t card_bin[8];
if (card.id.length() == 16 && hex2bin(card.id.c_str(), card_bin)) {
eamuse_card_insert(reader, card_bin);
}
}
}
+9 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#include <filesystem>
#include <optional>
#include "overlay/window.h"
@@ -10,6 +11,7 @@ namespace overlay::windows {
std::string name = "unnamed";
std::string id = "E004010000000000";
std::string search_string = "";
bool read_only = false;
float color[3] {};
};
@@ -29,7 +31,10 @@ namespace overlay::windows {
char name_buffer[65] {};
char card_buffer[17] {};
float color_buffer[3] {};
std::optional<CardEntry> card_cmd_overrides[2];
std::optional<CardEntry> loaded_card[2];
CardEntry *current_card = nullptr;
std::string search_filter = "";
@@ -41,10 +46,13 @@ namespace overlay::windows {
void generate_search_string(CardEntry *card);
void generate_random_color();
void build_card();
bool build_card(int reader);
void open_card_editor();
void build_card_editor();
void build_card_list();
void build_card_selectable(CardEntry &card);
void build_footer();
void insert_card_over_api(int reader, CardEntry &card);
};
}
File diff suppressed because it is too large Load Diff
+6 -4
View File
@@ -178,9 +178,11 @@ namespace overlay::windows {
static_cast<int>(ImGui::GetIO().DisplaySize.x),
static_cast<int>(ImGui::GetIO().DisplaySize.y));
// removed for size (along with setting IMGUI_DISABLE_DEMO_WINDOWS
// and IMGUI_DISABLE_DEBUG_TOOLS) - saves about 300kb in each
// binary
if (ImGui::Button("ImGui Debug Log")) {
overlay::SHOW_DEBUG_LOG_WINDOW = !overlay::SHOW_DEBUG_LOG_WINDOW;
}
// removed for size (IMGUI_DISABLE_DEMO_WINDOWS)
// metrics button
// this->metrics_open |= ImGui::Button("Metrics Window");
@@ -749,7 +751,7 @@ namespace overlay::windows {
if (ImGui::CollapsingHeader("Touch")) {
// status
ImGui::Text("Status: %s", is_touch_available() ? "available" : "unavailable");
ImGui::Text("Status: %s", is_touch_available("Control::touch_view") ? "available" : "unavailable");
// touch points
ImGui::SetNextItemOpen(true, ImGuiCond_Once);
+1 -1
View File
@@ -206,7 +206,7 @@ namespace overlay::windows {
bottom_right.x += overlay_content_top_left.x;
bottom_right.y += overlay_content_top_left.y;
ImGui::GetBackgroundDrawList()->AddImage(
reinterpret_cast<void *>(this->texture),
reinterpret_cast<ImTextureID>(this->texture),
overlay_content_top_left,
bottom_right);
+68 -16
View File
@@ -151,7 +151,7 @@ namespace overlay::windows {
url_fetch_errors += fmt::format("WinHttpSendRequest failed: {}\n", gle);
if (gle == 12175) {
url_fetch_errors += "\nThis is ERROR_WINHTTP_SECURE_FAILURE - most likely TLS 1.1 / TLS 1.2 error on old OS versions.\n\n";
url_fetch_errors += "Look up MSDN article on 'Update to enable TLS 1.1 and TLS 1.2 as default secure protocols in WinHTTP in Windows' for a fix.";
url_fetch_errors += "Look up MSDN article on 'Update to enable TLS 1.1 and TLS 1.2 as default secure protocols in WinHTTP in Windows' for a fix.\n";
}
return result;
}
@@ -183,7 +183,7 @@ namespace overlay::windows {
statusCode,
netutils::http_status_reason_phrase(statusCode));
if (statusCode == 404) {
url_fetch_errors += "(Patch server did not find any patches for this game version)";
url_fetch_errors += "(Patch server did not find any patches for this game version)\n";
}
return result;
}
@@ -219,7 +219,8 @@ namespace overlay::windows {
this->config_path = PATCH_MANAGER_CFG_PATH_OVERRIDE.value();
log_info("patchmanager", "using custom config file path: {}", this->config_path.string().c_str());
} else {
this->config_path = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools_patch_manager.json";
this->config_path =
fileutils::get_config_file_path("patchmanager", "spicetools_patch_manager.json");
}
if (!ldr_registered) {
@@ -550,7 +551,7 @@ namespace overlay::windows {
ImGui::Text("Failed to import patches from URL.");
if (!url_fetch_errors.empty()) {
ImGui::TextUnformatted("");
ImGui::PushTextWrapPos(ImGui::GetIO().DisplaySize.x * 0.5);
ImGui::PushTextWrapPos(ImGui::GetIO().DisplaySize.x * 0.7);
ImGui::TextUnformatted(url_fetch_errors.c_str());
ImGui::PopTextWrapPos();
}
@@ -558,6 +559,12 @@ namespace overlay::windows {
if (ImGui::Button("OK")) {
ImGui::CloseCurrentPopup();
}
if (!url_fetch_errors.empty()) {
ImGui::SameLine();
if (ImGui::Button("Copy Error")) {
clipboard::copy_text(url_fetch_errors);
}
}
ImGui::EndPopup();
}
@@ -649,17 +656,9 @@ namespace overlay::windows {
// first column, part 1: help / caution marker
ImGui::TableNextColumn();
const std::string description = patch.description;
const std::string caution = patch.caution;
if (!description.empty() && !caution.empty()) {
if (!patch.caution.empty()) {
ImGui::AlignTextToFramePadding();
ImGui::WarnMarker(description.c_str(), caution.c_str());
} else if (!description.empty()) {
ImGui::AlignTextToFramePadding();
ImGui::HelpMarker(description.c_str());
} else if (!caution.empty()) {
ImGui::AlignTextToFramePadding();
ImGui::WarnMarker(nullptr, caution.c_str());
ImGui::WarnMarker(patch.description.c_str(), patch.caution.c_str());
} else {
ImGui::DummyMarker();
}
@@ -705,6 +704,11 @@ namespace overlay::windows {
if (style_color_pushed) {
ImGui::PopStyleColor(style_color_pushed);
}
if (ImGui::IsItemHovered()) {
show_patch_tooltip(patch);
}
// show range after label for integer patches
if (patch.type == PatchType::Integer) {
ImGui::SameLine();
auto& numpatch = patch.patch_number;
@@ -741,6 +745,9 @@ namespace overlay::windows {
patch.last_status = is_patch_active(patch);
}
ImGui::EndDisabled();
if (ImGui::IsItemHovered()) {
show_patch_tooltip(patch);
}
// second column, part 2: additional options UI (dropdown, text input)
ImGui::SameLine();
@@ -767,6 +774,9 @@ namespace overlay::windows {
}
ImGui::EndCombo();
}
if (ImGui::IsItemHovered()) {
show_patch_tooltip(patch);
}
} else if (patch.type == PatchType::Integer) {
ImGui::SetNextItemWidth(200.0f);
auto& numpatch = patch.patch_number;
@@ -780,6 +790,9 @@ namespace overlay::windows {
apply_patch(patch, true);
config_dirty = true;
}
if (ImGui::IsItemHovered()) {
show_patch_tooltip(patch);
}
}
} else if (patch_status == PatchStatus::Disabled) {
ImGui::SetNextItemWidth(200.0f);
@@ -794,6 +807,9 @@ namespace overlay::windows {
ImGui::InputInt("##dummy_int_input", &patch.patch_number.value);
}
ImGui::EndDisabled();
if (ImGui::IsItemHovered()) {
show_patch_tooltip(patch);
}
}
} else {
ImGui::AlignTextToFramePadding();
@@ -822,6 +838,14 @@ namespace overlay::windows {
}
}
void PatchManager::show_patch_tooltip(const PatchData& patch) {
if (!patch.caution.empty()) {
ImGui::WarnTooltip(patch.description.c_str(), patch.caution.c_str());
} else if (!patch.description.empty()) {
ImGui::HelpTooltip(patch.description.c_str());
}
}
void PatchManager::hard_apply_patches() {
std::vector<std::string> written_list;
for (auto& patch : patches) {
@@ -1104,7 +1128,7 @@ namespace overlay::windows {
doc.Accept(writer);
// save to file
if (fileutils::text_write(config_path, buffer.GetString())) {
if (fileutils::write_config_file("patchmanager", config_path, buffer.GetString())) {
config_dirty = false;
} else {
log_warning("patchmanager", "unable to save config file");
@@ -1500,16 +1524,44 @@ namespace overlay::windows {
try {
auto patches_json = getFromUrl(dll_name, json_path);
if (!patches_json.empty()) {
if (!fileutils::dir_exists(LOCAL_PATCHES_PATH))
// see if this is valid JSON
Document doc_temp;
doc_temp.Parse(patches_json.c_str());
const auto error = doc_temp.GetParseError();
if (error) {
log_warning(
"patchmanager",
"remotely fetched JSON file parse error: {}",
rapidjson::GetParseError_En(error));
url_fetch_errors += fmt::format(
"Invalid JSON received from remote URL.\n"
"Your DLL version might not be supported.\n"
"URL: {}\n"
"JSON Parse Error: {}\n",
json_path,
rapidjson::GetParseError_En(error));
return false;
} else {
log_info("patchmanager", "remotely fetched JSON was successfully parsed");
}
// create patches dir
if (!fileutils::dir_exists(LOCAL_PATCHES_PATH)) {
fileutils::dir_create(LOCAL_PATCHES_PATH);
}
// save to file
std::filesystem::path save_path = LOCAL_PATCHES_PATH / (identifier + ".json");
fileutils::text_write(save_path, patches_json);
log_info("patchmanager", "remotely fetched JSON saved to: {}", save_path.string());
return true;
} else {
log_warning("patchmanager", "failed to fetch patches JSON for {}", dll_name);
}
} catch (const std::exception& e) {
log_warning("patchmanager", "exception occurred while loading remote patches JSON for {}: {}", dll_name, e.what());
url_fetch_errors += fmt::format(
"Exception while loading remote patches for {}: {}\n", dll_name, e.what());
}
return false;
}
@@ -142,6 +142,7 @@ namespace overlay::windows {
std::function<bool(const PatchData&)> filter = std::function<bool(const PatchData&)>(),
std::string pe_identifier_for_patch = "");
void show_patch_tooltip(const PatchData& patch);
};
PatchStatus is_patch_active(PatchData &patch);
+99 -21
View File
@@ -17,6 +17,10 @@ namespace overlay::windows {
this->init_pos = ImVec2(10, 10);
this->toggle_button = games::OverlayButtons::ToggleScreenResize;
this->toggle_screen_resize = games::OverlayButtons::ScreenResize;
this->toggle_scene[0] = games::OverlayButtons::ScreenResizeScene1;
this->toggle_scene[1] = games::OverlayButtons::ScreenResizeScene2;
this->toggle_scene[2] = games::OverlayButtons::ScreenResizeScene3;
this->toggle_scene[3] = games::OverlayButtons::ScreenResizeScene4;
}
ScreenResize::~ScreenResize() {
@@ -42,13 +46,16 @@ namespace overlay::windows {
void ScreenResize::reset_vars_to_default() {
cfg::SCREENRESIZE->enable_screen_resize = false;
cfg::SCREENRESIZE->screen_resize_current_scene = 0;
cfg::SCREENRESIZE->enable_linear_filter = true;
cfg::SCREENRESIZE->keep_aspect_ratio = true;
cfg::SCREENRESIZE->centered = true;
cfg::SCREENRESIZE->offset_x = 0;
cfg::SCREENRESIZE->offset_y = 0;
cfg::SCREENRESIZE->scale_x = 1.f;
cfg::SCREENRESIZE->scale_y = 1.f;
for (size_t i = 0; i < std::size(cfg::SCREENRESIZE->scene_settings); i++) {
auto& scene = cfg::SCREENRESIZE->scene_settings[i];
scene.keep_aspect_ratio = true;
scene.offset_x = 0;
scene.offset_y = 0;
scene.scale_x = 1.f;
scene.scale_y = 1.f;
}
cfg::SCREENRESIZE->enable_window_resize = false;
cfg::SCREENRESIZE->window_always_on_top = false;
@@ -88,28 +95,58 @@ namespace overlay::windows {
void ScreenResize::build_fullscreen_config() {
// enable checkbox
ImGui::TextWrapped("Hint: bind a key to Screen Resize for quickly toggling this on/off.");
ImGui::Checkbox("Enable", &cfg::SCREENRESIZE->enable_screen_resize);
ImGui::SameLine();
ImGui::HelpMarker("Hint: bind a key to Screen Resize for a quick toggle.");
ImGui::BeginDisabled(!cfg::SCREENRESIZE->enable_screen_resize);
ImGui::Checkbox("Linear Filter", &cfg::SCREENRESIZE->enable_linear_filter);
if (ImGui::RadioButton("Scene 1", cfg::SCREENRESIZE->screen_resize_current_scene == 0)) {
cfg::SCREENRESIZE->screen_resize_current_scene = 0;
}
ImGui::SameLine();
if (ImGui::RadioButton("Scene 2", cfg::SCREENRESIZE->screen_resize_current_scene == 1)) {
cfg::SCREENRESIZE->screen_resize_current_scene = 1;
}
ImGui::SameLine();
if (ImGui::RadioButton("Scene 3", cfg::SCREENRESIZE->screen_resize_current_scene == 2)) {
cfg::SCREENRESIZE->screen_resize_current_scene = 2;
}
ImGui::SameLine();
if (ImGui::RadioButton("Scene 4", cfg::SCREENRESIZE->screen_resize_current_scene == 3)) {
cfg::SCREENRESIZE->screen_resize_current_scene = 3;
}
ImGui::SameLine();
ImGui::HelpMarker(
"Hint: bind a key to Screen Resize 1/2/3/4 for quick scene switching. "
"Scene 1 is the default scene activated when starting the game.");
auto& scene = cfg::SCREENRESIZE->scene_settings[cfg::SCREENRESIZE->screen_resize_current_scene];
// general settings
ImGui::Checkbox("Linear Filter", &cfg::SCREENRESIZE->enable_linear_filter);
ImGui::Checkbox("Centered", &cfg::SCREENRESIZE->centered);
if (!cfg::SCREENRESIZE->centered) {
ImGui::InputInt("X Offset", &cfg::SCREENRESIZE->offset_x);
ImGui::InputInt("Y Offset", &cfg::SCREENRESIZE->offset_y);
}
ImGui::InputInt("X Offset", &scene.offset_x);
ImGui::SameLine();
ImGui::HelpMarker("Hint: ctrl + click on +/- buttons to move quickly.");
ImGui::InputInt("Y Offset", &scene.offset_y);
ImGui::SameLine();
ImGui::HelpMarker("Hint: ctrl + click on +/- buttons to move quickly.");
// aspect ratio
ImGui::Checkbox("Keep Aspect Ratio", &cfg::SCREENRESIZE->keep_aspect_ratio);
if (cfg::SCREENRESIZE->keep_aspect_ratio) {
if (ImGui::SliderFloat("Scale", &cfg::SCREENRESIZE->scale_x, 0.65f, 2.0f)) {
cfg::SCREENRESIZE->scale_y = cfg::SCREENRESIZE->scale_x;
ImGui::Checkbox("Keep Aspect Ratio", &scene.keep_aspect_ratio);
if (scene.keep_aspect_ratio) {
if (ImGui::SliderFloat("Scale", &scene.scale_x, 0.5f, 2.5f)) {
scene.scale_y = scene.scale_x;
}
ImGui::SameLine();
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value.");
} else {
ImGui::SliderFloat("Width Scale", &cfg::SCREENRESIZE->scale_x, 0.65f, 2.0f);
ImGui::SliderFloat("Height Scale", &cfg::SCREENRESIZE->scale_y, 0.65f, 2.0f);
ImGui::SliderFloat("Width Scale", &scene.scale_x, 0.5f, 2.5f);
ImGui::SameLine();
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value.");
ImGui::SliderFloat("Height Scale", &scene.scale_y, 0.5f, 2.5f);
ImGui::SameLine();
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value.");
}
ImGui::EndDisabled();
@@ -139,8 +176,16 @@ namespace overlay::windows {
if (ImGui::Checkbox("Always on Top", &cfg::SCREENRESIZE->window_always_on_top) ) {
graphics_update_z_order(window);
}
ImGui::BeginDisabled();
ImGui::Checkbox("Forced Render Scaling", &GRAPHICS_WINDOW_BACKBUFFER_SCALE);
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::HelpMarker(
"For windowed mode: forcibly set DX9 back buffer dimensions to match window size. "
"Reduces pixelated scaling artifacts. Works great on some games, but completely broken on others.\n\n"
"This can't be changed in-game; instead, set -windowscale option in spicecfg and restart.");
ImGui::Checkbox("Keep Aspect Ratio", &cfg::SCREENRESIZE->client_keep_aspect_ratio);
ImGui::TextWrapped("Hint: if game is blurry after resizing, try -windowscale option in spicecfg.");
ImGui::Checkbox("Manual window move/resize", &cfg::SCREENRESIZE->enable_window_resize);
ImGui::BeginDisabled(!cfg::SCREENRESIZE->enable_window_resize);
@@ -209,8 +254,10 @@ namespace overlay::windows {
void ScreenResize::update() {
Window::update();
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
// toggle
if (this->toggle_screen_resize != ~0u) {
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
bool toggle_screen_resize_new = overlay_buttons
&& this->overlay->hotkeys_triggered()
&& GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(this->toggle_screen_resize));
@@ -220,5 +267,36 @@ namespace overlay::windows {
}
this->toggle_screen_resize_state = toggle_screen_resize_new;
}
// scene switch
auto toggle_scene_state_new = ~0u;
for (size_t i = 0; i < std::size(this->toggle_scene); i++) {
if (this->toggle_scene[i] == ~0u) {
continue;
}
bool scene_switched = overlay_buttons
&& this->overlay->hotkeys_triggered()
&& GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(this->toggle_scene[i]));
if (scene_switched) {
toggle_scene_state_new = (uint32_t)i;
}
// only detect rising edges of key presses
if (scene_switched && (this->toggle_scene_state != i)) {
if (cfg::SCREENRESIZE->screen_resize_current_scene == (int8_t)i &&
cfg::SCREENRESIZE->enable_screen_resize) {
// this scene is already active, turn scaling off
cfg::SCREENRESIZE->enable_screen_resize = false;
} else {
// switch to scene
cfg::SCREENRESIZE->enable_screen_resize = true;
cfg::SCREENRESIZE->screen_resize_current_scene = i;
}
break;
}
}
// remember if a key was pressed (or nothing pressed) this frame
this->toggle_scene_state = toggle_scene_state_new;
}
}
@@ -16,6 +16,9 @@ namespace overlay::windows {
size_t toggle_screen_resize = ~0u;
bool toggle_screen_resize_state = false;
size_t toggle_scene[4] = { ~0u, ~0u, ~0u, ~0u };
uint32_t toggle_scene_state = ~0u;
void build_fullscreen_config();
void build_windowed_config();
void build_footer();
+26 -2
View File
@@ -23,11 +23,35 @@ namespace overlay::windows {
const auto padding = ImGui::GetFrameHeight() / 2;
this->init_size = ImVec2(ImGui::GetIO().DisplaySize.x - (padding * 2), 0.f);
this->init_size.y = (this->init_size.x * 9 / 16) + ImGui::GetFrameHeight();
switch (games::sdvx::OVERLAY_POS) {
case games::sdvx::SDVX_OVERLAY_BOTTOM_LEFT:
case games::sdvx::SDVX_OVERLAY_BOTTOM_RIGHT:
this->init_size.x = (ImGui::GetIO().DisplaySize.x - (ImGui::GetIO().DisplaySize.y * 9 / 16)) / 2 - padding;
this->init_size.y = (this->init_size.x * 9 / 16) + ImGui::GetFrameHeight();
break;
case games::sdvx::SDVX_OVERLAY_TOP:
case games::sdvx::SDVX_OVERLAY_BOTTOM:
case games::sdvx::SDVX_OVERLAY_MIDDLE:
default:
this->init_size = ImVec2(ImGui::GetIO().DisplaySize.x - (padding * 2), 0.f);
this->init_size.y = (this->init_size.x * 9 / 16) + ImGui::GetFrameHeight();
if (GRAPHICS_FS_ORIENTATION_SWAP) {
this->init_size.x /= 2;
this->init_size.y /= 2;
}
break;
}
this->init_pos = ImVec2(ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2, 0);
switch (games::sdvx::OVERLAY_POS) {
case games::sdvx::SDVX_OVERLAY_BOTTOM_LEFT:
this->init_pos.x = 0;
this->init_pos.y = ImGui::GetIO().DisplaySize.y - this->init_size.y;
break;
case games::sdvx::SDVX_OVERLAY_BOTTOM_RIGHT:
this->init_pos.x = ImGui::GetIO().DisplaySize.x - this->init_size.x;
this->init_pos.y = ImGui::GetIO().DisplaySize.y - this->init_size.y;
break;
case games::sdvx::SDVX_OVERLAY_TOP:
this->init_pos.y = padding;
break;
+1 -1
View File
@@ -494,7 +494,7 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
// get string index
ULONG string_index = 0;
if (button_caps.IsStringRange && button_caps.Range.StringMin != 0) {
string_index = button_caps.Range.StringMin + static_cast<ULONG>(button_output_caps_list.size()) - 1;
string_index = button_caps.Range.StringMin + static_cast<ULONG>(button_output_caps_names.size());
}
else if (!button_caps.IsStringRange && button_caps.NotRange.StringIndex != 0) {
string_index = button_caps.NotRange.StringIndex;
+25 -5
View File
@@ -138,8 +138,15 @@ static void touch_initialize() {
}
SPICETOUCH_INITIALIZED = true;
if (!RI_MGR) {
log_fatal(
LOG_MODULE_NAME,
"touch_initialize() - RI Manager not available, called too early! "
"This is a BUG in spice, please file a bug report with log.txt.");
}
// initialize handler
if (RI_MGR && rawinput::touch::is_enabled(RI_MGR.get())) {
if (rawinput::touch::is_enabled(RI_MGR.get())) {
TOUCH_HANDLER = new RawInputTouchHandler();
} else if (Win8Handler::is_available()) {
TOUCH_HANDLER = new Win8Handler();
@@ -166,13 +173,26 @@ static inline void touch_unregister_window(HWND hWnd) {
}
}
bool is_touch_available() {
bool is_touch_available(LPCSTR caller) {
static bool called_once = false;
if (!RI_MGR) {
log_fatal(
LOG_MODULE_NAME,
"is_touch_available({}) - RI Manager not available, called too early! "
"This is a BUG in spice, please file a bug report with log.txt.",
caller);
}
// initialize touch handler
touch_initialize();
// Check if a touch handler has been set. No need to call `is_available` here
// as `touch_initialize` does that.
if (!called_once) {
log_misc("touch", "is_touch_available called by: {}, returning {}",
caller, (TOUCH_HANDLER != nullptr) ? "TRUE" : "false");
called_once = true;
}
return TOUCH_HANDLER != nullptr;
}
@@ -202,7 +222,7 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
SPICETOUCH_REGISTERED_TOUCH = true;
// check if touch is available
if (is_touch_available()) {
if (is_touch_available("SpiceTouchWndProc")) {
// notify the handler of our window
TOUCH_HANDLER->window_register(hWnd);
+1 -1
View File
@@ -27,7 +27,7 @@ extern int SPICETOUCH_TOUCH_Y;
extern int SPICETOUCH_TOUCH_WIDTH;
extern int SPICETOUCH_TOUCH_HEIGHT;
bool is_touch_available();
bool is_touch_available(LPCSTR caller);
void touch_attach_wnd(HWND hWnd);
void touch_attach_dx_hook();

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