Compare commits

...

34 Commits

Author SHA1 Message Date
bicarus 220b27159b ea3: don't treat failure from dll_entry_init as fatal (#601)
## Link to GitHub Issue or related Pull Request, if one exists
fixes #600
2026-03-30 02:31:08 -07:00
bicarus b06a0a7d47 overlay: card override input should be disabled when overridden with cmd line (#599) 2026-03-29 20:36:42 -07:00
bicarus d66af8e16b overlay: options tab(s) clean up (#598)
## Description of change
Merge option name and command line parameter columns.
Expand the size of widget column.
In Search tab, add a new column to display the category.
Add a highlight effect to rows when hovered over - apply this to all
tables in the configurator UI.
2026-03-28 18:21:53 -07:00
bicarus-dev ad366ecdd0 update fatal crash message 2026-03-27 18:46:08 -07:00
llm96 22cb4d689c utils: print DLL info for early hooks (#597)
## Link to GitHub Issue or related Pull Request, if one exists
#596

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

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

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

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

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

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

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

## Description of change

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

N/A

## Description of change

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

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

## Testing

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

## Description of change

### Controller Presets

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

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

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

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

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

## Testing

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Description of change

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

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

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

n/a

## Description of change

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

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

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

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

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

## Description of change

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

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

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

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

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

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

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

## Testing
Tested with my own controller modifying USB descriptors... more
controller testing is needed.
2026-03-05 20:41:47 -08:00
59 changed files with 4829 additions and 758 deletions
+1
View File
@@ -58,6 +58,7 @@ Watch out for legacy OS compatibility. Currently, the minimum support floor is W
* Indents are four spaces.
* Try to keep under 100 characters per line. This includes comments and string literals.
* Avoid making unnecessary formatting or whitespace changes in a PR; e.g., watch out for text editor automatically stripping trailing whitespace. It pollutes the diff and slows down code review.
* For signed/unsigned integer types, prefer `cstdint` types; e.g., use `int32_t`, `uint64_t` and not `int` and `unsigned long long`.
* Always use \{ curly braces \} when appropriate; do not omit them even when it's optional; such as `for` `if` `else`, etc.
OK:
+17 -4
View File
@@ -109,6 +109,12 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# RapidJSON does this
add_compile_definitions(_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING)
add_compile_options("-Wno-nontrivial-memcall") # imgui, rapidjson
add_compile_options("-Wno-deprecated-builtins") # robin_hood
add_compile_options("-Wno-deprecated-declarations") # rapidjson
add_compile_options("-Wno-inconsistent-missing-override") # headsocket
add_compile_options("-Wno-microsoft-exception-spec") # gcc/clang have different noexcept specifiers on d3d9 COM interfaces
# warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunknown-warning-option")
@@ -260,6 +266,7 @@ set_source_files_properties(cfg/manifest.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/icon.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/Win32D.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(build/manifest64.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(stubs/manifest.rc PROPERTIES LANGUAGE RC)
# sources
#########
@@ -346,6 +353,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
cfg/configurator.cpp
cfg/configurator_wnd.cpp
cfg/screen_resize.cpp
cfg/controller_presets.cpp
# easrv
easrv/easrv.cpp
@@ -559,6 +567,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
overlay/windows/screen_resize.cpp
overlay/windows/sdvx_sub.cpp
overlay/windows/config.cpp
overlay/windows/controller_presets.cpp
overlay/windows/control.cpp
overlay/windows/eadev.cpp
overlay/windows/fps.cpp
@@ -805,7 +814,8 @@ endif()
# nvcuda.dll
set(SOURCE_FILES stubs/nvcuda.cpp)
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} stubs/nvcuda.def)
set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvcuda.def)
set_target_properties(spicetools_stubs_nvcuda PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvcuda PROPERTIES OUTPUT_NAME "nvcuda")
@@ -815,7 +825,8 @@ endif()
# nvcuvid.dll
set(SOURCE_FILES stubs/nvcuvid.cpp)
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} stubs/nvcuvid.def)
set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvcuvid.def)
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES OUTPUT_NAME "nvcuvid")
@@ -825,7 +836,8 @@ endif()
# nvEncodeAPI64.dll
set(SOURCE_FILES stubs/nvEncodeAPI64.cpp)
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} stubs/nvEncodeAPI64.def)
set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvEncodeAPI64.def)
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES OUTPUT_NAME "nvEncodeAPI64")
@@ -835,7 +847,8 @@ endif()
# cpusbxpkm.dll (32 bit)
set(SOURCE_FILES stubs/cpusbxpkm.cpp)
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} stubs/cpusbxpkm.def)
set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/cpusbxpkm.def)
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES OUTPUT_NAME "cpusbxpkm")
+27 -3
View File
@@ -217,7 +217,23 @@ namespace avs {
ea3_config_name = "prop/eamuse-config.xml";
}
log_info("avs-ea3", "booting (using {})", ea3_config_name);
if (avs::core::file_exists(ea3_config_name)) {
log_info("avs-ea3", "booting (using {})", ea3_config_name);
} else {
log_warning("avs-ea3", "looked for the following files in order:");
log_warning("avs-ea3", " * prop/ea3-config.xml");
log_warning("avs-ea3", " * prop/ea3-cfg.xml");
if (avs::game::DLL_NAME == "beatstream1.dll") {
log_warning("avs-ea3", " * prop/ea3-config-1.xml");
}
if (avs::game::DLL_NAME == "beatstream2.dll") {
log_warning("avs-ea3", " * prop/ea3-config-2.xml");
}
log_warning("avs-ea3", " * prop/eamuse-config.xml");
log_warning("avs-ea3", "none of the files above were found, game will fail to boot");
log_warning("avs-ea3", "usually, this means your game data is incomplete");
log_fatal("avs-ea3", "no ea3 config file found in prop directory; check log messages");
}
// remember new config path
CFG_PATH = ea3_config_name;
@@ -554,9 +570,17 @@ namespace avs {
auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param");
// call the game init
log_info("avs-ea3", "calling entry init (init code = {})", init_code_str);
log_info("avs-ea3", "calling dll_entry_init (init code = {})", init_code_str);
if (!avs::game::entry_init(init_code_str.data(), app_param)) {
log_fatal("avs-ea3", "entry init failed :(");
for (size_t i = 0; i < 255; i++) {
log_warning("avs-ea3", "dll_entry_init failed :(");
}
deferredlogs::defer_error_messages({
"dll_entry_init failed",
" init code: " + init_code_str,
" this is a fatal error that causes boot failure",
" most likely, your prop files have errors"
});
}
// accommodate changes to soft id code
+327
View File
@@ -0,0 +1,327 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<!-- PHOENIXWAN controller preset for Beatmania IIDX
Button indices 0-6: keys 1-7
Button indices 8-11: E1 (Start for Deluxe Model Style), E2 (Effect), E3 (VEFX), E4 (P1 Start for Lightning Model Style)
P1 Side P2 Side
8 9 10 11 8 9 10 11
2 4 6 2 4 6
TT 1 3 5 7 1 3 5 7 TT -->
<template name="DJ DAO PHOENIXWAN" game="Beatmania IIDX">
<buttons>
<button name="P1 1" vkey="0" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 2" vkey="1" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 3" vkey="2" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 4" vkey="3" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 5" vkey="4" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 6" vkey="5" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 7" vkey="6" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 1" vkey="0" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 2" vkey="1" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 3" vkey="2" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 4" vkey="3" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 5" vkey="4" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 6" vkey="5" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 7" vkey="6" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<!-- P1 Start: E1 primary (index 8), E4 alternative (index 11) -->
<button name="P1 Start" vkey="8" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 Start" vkey="11" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<!-- P2 Start: E1 primary (index 8), E4 alternative (index 11) -->
<button name="P2 Start" vkey="8" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 Start" vkey="11" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<!-- EFFECT: E2 (index 9), primary P2 side, alternative P1 side -->
<button name="EFFECT" vkey="9" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="EFFECT" vkey="9" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<!-- VEFX: E3 (index 10), primary P2 side, alternative P1 side -->
<button name="VEFX" vkey="10" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="VEFX" vkey="10" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<analogs>
<analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights>
<light name="P1 1" devid="Player 1" index="0"/>
<light name="P1 2" devid="Player 1" index="1"/>
<light name="P1 3" devid="Player 1" index="2"/>
<light name="P1 4" devid="Player 1" index="3"/>
<light name="P1 5" devid="Player 1" index="4"/>
<light name="P1 6" devid="Player 1" index="5"/>
<light name="P1 7" devid="Player 1" index="6"/>
<light name="P2 1" devid="Player 2" index="0"/>
<light name="P2 2" devid="Player 2" index="1"/>
<light name="P2 3" devid="Player 2" index="2"/>
<light name="P2 4" devid="Player 2" index="3"/>
<light name="P2 5" devid="Player 2" index="4"/>
<light name="P2 6" devid="Player 2" index="5"/>
<light name="P2 7" devid="Player 2" index="6"/>
<!-- Start buttons bind both E1 (index 8) and E4 (index 11), but lights use
Lightning Model style only (one per side) to avoid both lighting up at once -->
<light name="P1 Start" devid="Player 1" index="10"/>
<light name="P2 Start" devid="Player 2" index="7"/>
<light name="VEFX" devid="Player 1" index="8"/>
<light name="VEFX" devid="Player 2" index="8"/>
<light name="Effect" devid="Player 1" index="9"/>
<light name="Effect" devid="Player 2" index="9"/>
</lights>
</template>
<!-- Common "double scratch" BMS playstyle: LSHIFT A S D SPACE L ; ' RSHIFT -->
<!-- Uses Up/Down arrows for turntable navigation -->
<template name="Keyboard (linear, double scratch, QWERTY)" game="Beatmania IIDX">
<buttons>
<button name="P1 1" vkey="65" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 2" vkey="83" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 3" vkey="68" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 4" vkey="32" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 5" vkey="76" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 6" vkey="186" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 7" vkey="222" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 TT+" vkey="40" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 TT-" vkey="38" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 TT+/-" vkey="160" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 TT+/- Alternate" vkey="161" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 Start" vkey="13" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 Start" vkey="69" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="EFFECT" vkey="81" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="VEFX" vkey="87" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<keypad_buttons/>
<analogs/>
<lights/>
</template>
<template name="arcin (arcin-infinitas)" game="Beatmania IIDX">
<buttons>
<button name="P1 1" vkey="0" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 2" vkey="1" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 3" vkey="2" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 4" vkey="3" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 5" vkey="4" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 6" vkey="5" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 7" vkey="6" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 Start" vkey="9" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 1" vkey="0" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 2" vkey="1" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 3" vkey="2" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 4" vkey="3" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 5" vkey="4" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 6" vkey="5" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 7" vkey="6" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 Start" vkey="9" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="EFFECT" vkey="11" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="EFFECT" vkey="11" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="VEFX" vkey="10" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="VEFX" vkey="10" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<keypad_buttons/>
<!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. -->
<analogs>
<analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights>
<light name="P1 1" devid="arcin (1p)" index="0"/>
<light name="P1 2" devid="arcin (1p)" index="1"/>
<light name="P1 3" devid="arcin (1p)" index="2"/>
<light name="P1 4" devid="arcin (1p)" index="3"/>
<light name="P1 5" devid="arcin (1p)" index="4"/>
<light name="P1 6" devid="arcin (1p)" index="5"/>
<light name="P1 7" devid="arcin (1p)" index="6"/>
<light name="P2 1" devid="arcin (2p)" index="0"/>
<light name="P2 2" devid="arcin (2p)" index="1"/>
<light name="P2 3" devid="arcin (2p)" index="2"/>
<light name="P2 4" devid="arcin (2p)" index="3"/>
<light name="P2 5" devid="arcin (2p)" index="4"/>
<light name="P2 6" devid="arcin (2p)" index="5"/>
<light name="P2 7" devid="arcin (2p)" index="6"/>
<light name="P1 Start" devid="arcin (1p)" index="9"/>
<light name="P2 Start" devid="arcin (2p)" index="9"/>
</lights>
</template>
<!-- This controller has many modes; here, we use HID Analog mode which is meant for data -->
<template name="DJ DAO Nostroller (Analog mode)" game="Nostalgia">
<buttons/>
<keypad_buttons/>
<analogs>
<analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights>
<light name="Key 1 R" devid="Lights 01-14" index="0"/>
<light name="Key 1 G" devid="Lights 01-14" index="1"/>
<light name="Key 1 B" devid="Lights 01-14" index="2"/>
<light name="Key 2 R" devid="Lights 01-14" index="3"/>
<light name="Key 2 G" devid="Lights 01-14" index="4"/>
<light name="Key 2 B" devid="Lights 01-14" index="5"/>
<light name="Key 3 R" devid="Lights 01-14" index="6"/>
<light name="Key 3 G" devid="Lights 01-14" index="7"/>
<light name="Key 3 B" devid="Lights 01-14" index="8"/>
<light name="Key 4 R" devid="Lights 01-14" index="9"/>
<light name="Key 4 G" devid="Lights 01-14" index="10"/>
<light name="Key 4 B" devid="Lights 01-14" index="11"/>
<light name="Key 5 R" devid="Lights 01-14" index="12"/>
<light name="Key 5 G" devid="Lights 01-14" index="13"/>
<light name="Key 5 B" devid="Lights 01-14" index="14"/>
<light name="Key 6 R" devid="Lights 01-14" index="15"/>
<light name="Key 6 G" devid="Lights 01-14" index="16"/>
<light name="Key 6 B" devid="Lights 01-14" index="17"/>
<light name="Key 7 R" devid="Lights 01-14" index="18"/>
<light name="Key 7 G" devid="Lights 01-14" index="19"/>
<light name="Key 7 B" devid="Lights 01-14" index="20"/>
<light name="Key 8 R" devid="Lights 01-14" index="21"/>
<light name="Key 8 G" devid="Lights 01-14" index="22"/>
<light name="Key 8 B" devid="Lights 01-14" index="23"/>
<light name="Key 9 R" devid="Lights 01-14" index="24"/>
<light name="Key 9 G" devid="Lights 01-14" index="25"/>
<light name="Key 9 B" devid="Lights 01-14" index="26"/>
<light name="Key 10 R" devid="Lights 01-14" index="27"/>
<light name="Key 10 G" devid="Lights 01-14" index="28"/>
<light name="Key 10 B" devid="Lights 01-14" index="29"/>
<light name="Key 11 R" devid="Lights 01-14" index="30"/>
<light name="Key 11 G" devid="Lights 01-14" index="31"/>
<light name="Key 11 B" devid="Lights 01-14" index="32"/>
<light name="Key 12 R" devid="Lights 01-14" index="33"/>
<light name="Key 12 G" devid="Lights 01-14" index="34"/>
<light name="Key 12 B" devid="Lights 01-14" index="35"/>
<light name="Key 13 R" devid="Lights 01-14" index="36"/>
<light name="Key 13 G" devid="Lights 01-14" index="37"/>
<light name="Key 13 B" devid="Lights 01-14" index="38"/>
<light name="Key 14 R" devid="Lights 01-14" index="39"/>
<light name="Key 14 G" devid="Lights 01-14" index="40"/>
<light name="Key 14 B" devid="Lights 01-14" index="41"/>
<light name="Key 15 R" devid="Lights 15-28" index="0"/>
<light name="Key 15 G" devid="Lights 15-28" index="1"/>
<light name="Key 15 B" devid="Lights 15-28" index="2"/>
<light name="Key 16 R" devid="Lights 15-28" index="3"/>
<light name="Key 16 G" devid="Lights 15-28" index="4"/>
<light name="Key 16 B" devid="Lights 15-28" index="5"/>
<light name="Key 17 R" devid="Lights 15-28" index="6"/>
<light name="Key 17 G" devid="Lights 15-28" index="7"/>
<light name="Key 17 B" devid="Lights 15-28" index="8"/>
<light name="Key 18 R" devid="Lights 15-28" index="9"/>
<light name="Key 18 G" devid="Lights 15-28" index="10"/>
<light name="Key 18 B" devid="Lights 15-28" index="11"/>
<light name="Key 19 R" devid="Lights 15-28" index="12"/>
<light name="Key 19 G" devid="Lights 15-28" index="13"/>
<light name="Key 19 B" devid="Lights 15-28" index="14"/>
<light name="Key 20 R" devid="Lights 15-28" index="15"/>
<light name="Key 20 G" devid="Lights 15-28" index="16"/>
<light name="Key 20 B" devid="Lights 15-28" index="17"/>
<light name="Key 21 R" devid="Lights 15-28" index="18"/>
<light name="Key 21 G" devid="Lights 15-28" index="19"/>
<light name="Key 21 B" devid="Lights 15-28" index="20"/>
<light name="Key 22 R" devid="Lights 15-28" index="21"/>
<light name="Key 22 G" devid="Lights 15-28" index="22"/>
<light name="Key 22 B" devid="Lights 15-28" index="23"/>
<light name="Key 23 R" devid="Lights 15-28" index="24"/>
<light name="Key 23 G" devid="Lights 15-28" index="25"/>
<light name="Key 23 B" devid="Lights 15-28" index="26"/>
<light name="Key 24 R" devid="Lights 15-28" index="27"/>
<light name="Key 24 G" devid="Lights 15-28" index="28"/>
<light name="Key 24 B" devid="Lights 15-28" index="29"/>
<light name="Key 25 R" devid="Lights 15-28" index="30"/>
<light name="Key 25 G" devid="Lights 15-28" index="31"/>
<light name="Key 25 B" devid="Lights 15-28" index="32"/>
<light name="Key 26 R" devid="Lights 15-28" index="33"/>
<light name="Key 26 G" devid="Lights 15-28" index="34"/>
<light name="Key 26 B" devid="Lights 15-28" index="35"/>
<light name="Key 27 R" devid="Lights 15-28" index="36"/>
<light name="Key 27 G" devid="Lights 15-28" index="37"/>
<light name="Key 27 B" devid="Lights 15-28" index="38"/>
<light name="Key 28 R" devid="Lights 15-28" index="39"/>
<light name="Key 28 G" devid="Lights 15-28" index="40"/>
<light name="Key 28 B" devid="Lights 15-28" index="41"/>
</lights>
</template>
<!-- Gitaller has two modes - digital tilt and analog axis. This binds both of them at the same time. -->
<template name="DJ DAO Gitaller" game="GitaDora">
<buttons>
<button name="Guitar P1 Start" vkey="9" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Help" vkey="8" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Pick Up" vkey="5" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Pick Down" vkey="6" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 R" vkey="0" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 G" vkey="1" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 B" vkey="2" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Y" vkey="3" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 P" vkey="4" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Wail Up" vkey="10" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Wail Down" vkey="11" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<keypad_buttons/>
<analogs>
<analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights/>
<!-- Gen 1 FAUCETWO in HID mode; should also work for later gens -->
</template>
<template name="DJ DAO FAUCETWO" game="Sound Voltex">
<buttons>
<button name="BT-A" vkey="1" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="BT-B" vkey="2" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="BT-C" vkey="3" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="BT-D" vkey="4" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="FX-L" vkey="5" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="FX-R" vkey="6" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Start" vkey="0" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<keypad_buttons/>
<analogs>
<analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights>
<light name="BT-A" devid="Lights (MI_02)" index="24"/>
<light name="BT-B" devid="Lights (MI_02)" index="25"/>
<light name="BT-C" devid="Lights (MI_02)" index="26"/>
<light name="BT-D" devid="Lights (MI_02)" index="27"/>
<light name="FX-L" devid="Lights (MI_02)" index="28"/>
<light name="FX-R" devid="Lights (MI_02)" index="29"/>
<light name="Start" devid="Lights (MI_02)" index="30"/>
<light name="Upper Left Speaker Avg R" devid="Lights (MI_02)" index="0"/>
<light name="Upper Left Speaker Avg G" devid="Lights (MI_02)" index="1"/>
<light name="Upper Left Speaker Avg B" devid="Lights (MI_02)" index="2"/>
<light name="Upper Right Speaker Avg R" devid="Lights (MI_02)" index="9"/>
<light name="Upper Right Speaker Avg G" devid="Lights (MI_02)" index="10"/>
<light name="Upper Right Speaker Avg B" devid="Lights (MI_02)" index="11"/>
<light name="Lower Left Speaker Avg R" devid="Lights (MI_02)" index="3"/>
<light name="Lower Left Speaker Avg G" devid="Lights (MI_02)" index="4"/>
<light name="Lower Left Speaker Avg B" devid="Lights (MI_02)" index="5"/>
<light name="Lower Right Speaker Avg R" devid="Lights (MI_02)" index="6"/>
<light name="Lower Right Speaker Avg G" devid="Lights (MI_02)" index="7"/>
<light name="Lower Right Speaker Avg B" devid="Lights (MI_02)" index="8"/>
<light name="Control Panel Avg R" devid="Lights (MI_02)" index="15"/>
<light name="Control Panel Avg G" devid="Lights (MI_02)" index="16"/>
<light name="Control Panel Avg B" devid="Lights (MI_02)" index="17"/>
</lights>
</template>
</templates>
+2 -2
View File
@@ -41,8 +41,8 @@ done
# settings
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
GIT_HEAD=$(git rev-parse HEAD || echo "none")
TOOLCHAIN_32="/usr/share/mingw/toolchain-i686-w64-mingw32.cmake"
TOOLCHAIN_64="/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake"
TOOLCHAIN_32="${TOOLCHAIN_32:-"/usr/share/mingw/toolchain-i686-w64-mingw32.cmake"}"
TOOLCHAIN_64="${TOOLCHAIN_64:-"/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake"}"
BUILDDIR_32_RELEASE="./cmake-build-release-32"
BUILDDIR_32_DEBUG="./cmake-build-debug-32"
BUILDDIR_64_RELEASE="./cmake-build-release-64"
+1
View File
@@ -13,5 +13,6 @@ IDR_LICENSES RCDATA "../licenses.txt"
IDR_PATCHES RCDATA "../build/patches.json"
IDR_README RCDATA "../readme.txt"
IDR_DSEGFONT RCDATA "../external/dseg/DSEG14Classic-Italic.ttf"
IDR_CONTROLLER_PRESETS_BUILTIN RCDATA "../build/controller_presets.xml"
#endif
+1
View File
@@ -91,6 +91,7 @@ public:
setMultiplier(1);
setRelativeMode(false);
setDelayBufferDepth(0);
setLastState(0.5f);
}
inline void clearBindings() {
+44 -1
View File
@@ -37,7 +37,11 @@ std::vector<Button> GameAPI::Buttons::sortButtons(
for (auto &bt : buttons) {
if (name == bt.getName()) {
button_found = true;
sorted.push_back(bt);
Button button_new = bt;
if (vkey_defaults) {
button_new.setVKeyDefault(vkey_defaults->at(index));
}
sorted.push_back(button_new);
break;
}
}
@@ -47,6 +51,7 @@ std::vector<Button> GameAPI::Buttons::sortButtons(
if (vkey_defaults) {
button.setVKey(vkey_defaults->at(index));
button.setVKeyDefault(vkey_defaults->at(index));
}
}
@@ -861,6 +866,44 @@ std::vector<Light> GameAPI::Lights::sortLights(
return sorted;
}
static std::vector<Light> sortLightsWithCategoryInternal(
const std::vector<Light> &lights,
const std::initializer_list<GameAPI::Lights::LightAndCategory> list)
{
std::vector<Light> sorted;
bool light_found;
for (auto &name : list) {
light_found = false;
for (auto &light : lights) {
if (name.light_name == light.getName()) {
light_found = true;
Light light_new = light;
light_new.setCategory(name.category_name);
sorted.push_back(light_new);
break;
}
}
if (!light_found) {
sorted.emplace_back(name.light_name, name.category_name);
}
}
return sorted;
}
void GameAPI::Lights::sortLightsWithCategory(
std::vector<Light> *lights,
const std::initializer_list<LightAndCategory> list) {
if (lights) {
*lights = sortLightsWithCategoryInternal(*lights, list);
}
}
void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float value) {
// check device
+15 -21
View File
@@ -6,24 +6,20 @@
#include <windows.h>
#include "option.h"
#include "button.h"
#include "analog.h"
#include "light.h"
namespace rawinput {
class RawInputManager;
struct Device;
}
class Button;
class Analog;
class Light;
class Game;
class Option;
namespace GameAPI {
namespace Buttons {
enum State {
BUTTON_PRESSED = true,
BUTTON_NOT_PRESSED = false
};
/**
* Parses the config and returns the buttons set by the user.
*
@@ -113,21 +109,13 @@ namespace GameAPI {
}
namespace Lights {
std::vector<Light> getLights(const std::string &game_name);
std::vector<Light> sortLights(
const std::vector<Light> &lights,
const std::vector<std::string> &light_names);
template<typename T>
void sortLights(std::vector<Light> *lights, T t) {
const std::vector<std::string> light_names { t };
if (lights) {
*lights = GameAPI::Lights::sortLights(*lights, light_names);
}
}
template<typename T, typename... Rest>
void sortLights(std::vector<Light> *lights, T t, Rest... rest) {
const std::vector<std::string> light_names { t, rest... };
@@ -137,6 +125,16 @@ namespace GameAPI {
}
}
struct LightAndCategory {
const std::string category_name;
const std::string light_name;
LightAndCategory(std::string category_name, std::string light_name) :
category_name(std::move(category_name)), light_name(std::move(light_name)) {}
};
void sortLightsWithCategory(std::vector<Light> *lights, const std::initializer_list<LightAndCategory> list);
void writeLight(rawinput::Device *device, int index, float value);
void writeLight(rawinput::RawInputManager *manager, Light &light, float value);
void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value);
@@ -152,7 +150,3 @@ namespace GameAPI {
void sortOptions(std::vector<Option> &, const std::vector<OptionDefinition> &);
}
}
#include "button.h"
#include "analog.h"
#include "light.h"
+17 -17
View File
@@ -44,7 +44,7 @@ std::string Button::getVKeyString() {
case 0x0C:
return "Clear";
case 0x0D:
return "Enter";
return "Enter/Return";
case 0x10:
return "Shift";
case 0x11:
@@ -183,37 +183,37 @@ std::string Button::getVKeyString() {
case 0x5D:
return "Apps";
case 0x60:
return "Num 0";
return "Numpad 0";
case 0x61:
return "Num 1";
return "Numpad 1";
case 0x62:
return "Num 2";
return "Numpad 2";
case 0x63:
return "Num 3";
return "Numpad 3";
case 0x64:
return "Num 4";
return "Numpad 4";
case 0x65:
return "Num 5";
return "Numpad 5";
case 0x66:
return "Num 6";
return "Numpad 6";
case 0x67:
return "Num 7";
return "Numpad 7";
case 0x68:
return "Num 8";
return "Numpad 8";
case 0x69:
return "Num 9";
return "Numpad 9";
case 0x6A:
return "*";
return "Numpad *";
case 0x6B:
return "+";
return "Numpad +";
case 0x6C:
return "Seperator";
return "Numpad Seperator";
case 0x6D:
return "-";
return "Numpad -";
case 0x6E:
return ".";
return "Numpad .";
case 0x6F:
return "/";
return "Numpad /";
case 0x70:
return "F1";
case 0x71:
+23 -3
View File
@@ -1,15 +1,23 @@
#pragma once
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include "api.h"
namespace rawinput {
class RawInputManager;
}
namespace GameAPI {
namespace Buttons {
enum State {
BUTTON_PRESSED = true,
BUTTON_NOT_PRESSED = false
};
}
}
enum ButtonAnalogType {
BAT_NONE = 0,
BAT_POSITIVE = 1,
@@ -41,6 +49,10 @@ private:
std::string name;
std::string device_identifier = "";
unsigned short vKey = INVALID_VKEY;
// default bindings are always naive
unsigned short vKey_default = INVALID_VKEY;
ButtonAnalogType analog_type = BAT_NONE;
double debounce_up = 0.0;
double debounce_down = 0.0;
@@ -51,10 +63,10 @@ private:
float last_velocity = 0.f;
unsigned short velocity_threshold = 0;
std::string getVKeyString();
std::string getMidiNoteString();
public:
std::string getVKeyString();
// overrides
bool override_enabled = false;
@@ -117,6 +129,14 @@ public:
this->vKey = vKey;
}
inline unsigned short getVKeyDefault() const {
return this->vKey_default;
}
inline void setVKeyDefault(unsigned short vKey_default) {
this->vKey_default = vKey_default;
}
inline ButtonAnalogType getAnalogType() const {
return this->analog_type;
}
+460
View File
@@ -0,0 +1,460 @@
#include "overlay/windows/controller_presets.h"
#include <filesystem>
#include "cfg/resource.h"
#include "external/tinyxml2/tinyxml2.h"
#include "util/fileutils.h"
#include "util/logging.h"
#include "util/resutils.h"
namespace overlay::windows {
static std::filesystem::path get_templates_path() {
return fileutils::get_config_file_path("ControllerPresets", "spicetools_presets.xml");
}
static void write_button_entry(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent,
const std::string &name, const TemplateButtonEntry &entry) {
auto *el = doc.NewElement("button");
el->SetAttribute("name", name.c_str());
el->SetAttribute("vkey", entry.vKey);
el->SetAttribute("analogtype", (int)entry.analog_type);
el->SetAttribute("devid", entry.device_identifier.c_str());
el->SetAttribute("invert", entry.invert);
el->SetAttribute("debounce_up", entry.debounce_up);
el->SetAttribute("debounce_down", entry.debounce_down);
el->SetAttribute("velocity_threshold", entry.velocity_threshold);
parent->InsertEndChild(el);
}
static TemplateButtonEntry read_button_entry(tinyxml2::XMLElement *el) {
TemplateButtonEntry entry;
int vkey = INVALID_VKEY;
el->QueryIntAttribute("vkey", &vkey);
entry.vKey = (unsigned short)vkey;
int atype = 0;
el->QueryIntAttribute("analogtype", &atype);
entry.analog_type = (ButtonAnalogType)atype;
const char *devid = el->Attribute("devid");
entry.device_identifier = devid ? devid : "";
el->QueryBoolAttribute("invert", &entry.invert);
el->QueryDoubleAttribute("debounce_up", &entry.debounce_up);
el->QueryDoubleAttribute("debounce_down", &entry.debounce_down);
int vel = 0;
el->QueryIntAttribute("velocity_threshold", &vel);
entry.velocity_threshold = (unsigned short)vel;
return entry;
}
static void write_analog(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent,
const TemplateAnalogBinding &analog) {
auto *el = doc.NewElement("analog");
el->SetAttribute("name", analog.name.c_str());
el->SetAttribute("devid", analog.device_identifier.c_str());
el->SetAttribute("index", analog.index);
el->SetAttribute("sensivity", analog.sensitivity); // keep typo for compat
el->SetAttribute("deadzone", analog.deadzone);
el->SetAttribute("deadzone_mirror", analog.deadzone_mirror);
el->SetAttribute("invert", analog.invert);
el->SetAttribute("smoothing", analog.smoothing);
el->SetAttribute("multiplier", analog.multiplier);
el->SetAttribute("relative", analog.relative_mode);
el->SetAttribute("delay", analog.delay_buffer_depth);
parent->InsertEndChild(el);
}
static TemplateAnalogBinding read_analog(tinyxml2::XMLElement *el) {
TemplateAnalogBinding a;
const char *name = el->Attribute("name");
a.name = name ? name : "";
const char *devid = el->Attribute("devid");
a.device_identifier = devid ? devid : "";
int idx = 0xFF;
el->QueryIntAttribute("index", &idx);
a.index = (unsigned short)idx;
el->QueryFloatAttribute("sensivity", &a.sensitivity); // keep typo
el->QueryFloatAttribute("deadzone", &a.deadzone);
el->QueryBoolAttribute("deadzone_mirror", &a.deadzone_mirror);
el->QueryBoolAttribute("invert", &a.invert);
el->QueryBoolAttribute("smoothing", &a.smoothing);
el->QueryIntAttribute("multiplier", &a.multiplier);
el->QueryBoolAttribute("relative", &a.relative_mode);
el->QueryIntAttribute("delay", &a.delay_buffer_depth);
return a;
}
static void write_light_entry(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent,
const std::string &name, const TemplateLightEntry &entry) {
auto *el = doc.NewElement("light");
el->SetAttribute("name", name.c_str());
el->SetAttribute("devid", entry.device_identifier.c_str());
el->SetAttribute("index", entry.index);
parent->InsertEndChild(el);
}
static TemplateLightEntry read_light_entry(tinyxml2::XMLElement *el) {
TemplateLightEntry entry;
const char *devid = el->Attribute("devid");
entry.device_identifier = devid ? devid : "";
int idx = 0;
el->QueryIntAttribute("index", &idx);
entry.index = (unsigned int)idx;
return entry;
}
// Parse all templates from an already-loaded XMLDocument.
// is_builtin controls the is_builtin flag on each returned template.
static std::vector<ControllerTemplate> load_templates_from_doc(
tinyxml2::XMLDocument &doc, bool is_builtin) {
std::vector<ControllerTemplate> templates;
auto *root = doc.RootElement();
if (!root) {
return templates;
}
auto *tmpl_el = root->FirstChildElement("template");
while (tmpl_el) {
ControllerTemplate tmpl;
const char *name = tmpl_el->Attribute("name");
const char *game = tmpl_el->Attribute("game");
tmpl.name = name ? name : "";
tmpl.game_name = game ? game : "";
tmpl.is_builtin = is_builtin;
// buttons
auto *buttons_el = tmpl_el->FirstChildElement("buttons");
if (buttons_el) {
auto *btn_el = buttons_el->FirstChildElement("button");
while (btn_el) {
const char *btn_name = btn_el->Attribute("name");
std::string btn_name_str = btn_name ? btn_name : "";
TemplateButtonBinding *binding = nullptr;
for (auto &b : tmpl.buttons) {
if (b.name == btn_name_str) { binding = &b; break; }
}
if (!binding) {
tmpl.buttons.push_back({btn_name_str, read_button_entry(btn_el), {}});
} else {
binding->alternatives.push_back(read_button_entry(btn_el));
}
btn_el = btn_el->NextSiblingElement("button");
}
}
// keypad buttons
auto *kp_el = tmpl_el->FirstChildElement("keypad_buttons");
if (kp_el) {
auto *btn_el = kp_el->FirstChildElement("button");
while (btn_el) {
const char *btn_name = btn_el->Attribute("name");
std::string btn_name_str = btn_name ? btn_name : "";
TemplateButtonBinding *binding = nullptr;
for (auto &b : tmpl.keypad_buttons) {
if (b.name == btn_name_str) { binding = &b; break; }
}
if (!binding) {
tmpl.keypad_buttons.push_back({btn_name_str, read_button_entry(btn_el), {}});
} else {
binding->alternatives.push_back(read_button_entry(btn_el));
}
btn_el = btn_el->NextSiblingElement("button");
}
}
// analogs
auto *analogs_el = tmpl_el->FirstChildElement("analogs");
if (analogs_el) {
auto *analog_el = analogs_el->FirstChildElement("analog");
while (analog_el) {
tmpl.analogs.push_back(read_analog(analog_el));
analog_el = analog_el->NextSiblingElement("analog");
}
}
// lights
auto *lights_el = tmpl_el->FirstChildElement("lights");
if (lights_el) {
auto *light_el = lights_el->FirstChildElement("light");
while (light_el) {
const char *light_name = light_el->Attribute("name");
std::string light_name_str = light_name ? light_name : "";
TemplateLightBinding *binding = nullptr;
for (auto &l : tmpl.lights) {
if (l.name == light_name_str) { binding = &l; break; }
}
if (!binding) {
tmpl.lights.push_back({light_name_str, read_light_entry(light_el), {}});
} else {
binding->alternatives.push_back(read_light_entry(light_el));
}
light_el = light_el->NextSiblingElement("light");
}
}
templates.push_back(std::move(tmpl));
tmpl_el = tmpl_el->NextSiblingElement("template");
}
return templates;
}
static std::vector<ControllerTemplate> load_builtin_templates() {
auto xml = resutil::load_file_string(IDR_CONTROLLER_PRESETS_BUILTIN);
tinyxml2::XMLDocument doc;
auto err = doc.Parse(xml.c_str());
if (err != tinyxml2::XML_SUCCESS) {
log_warning("templates", "failed to parse builtin templates XML: {}", (int)err);
return {};
}
return load_templates_from_doc(doc, true);
}
std::vector<ControllerTemplate> load_user_templates() {
std::vector<ControllerTemplate> templates;
auto path = get_templates_path();
tinyxml2::XMLDocument doc;
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) {
return templates;
}
return load_templates_from_doc(doc, false);
}
bool save_user_template(
const ControllerTemplate &tmpl,
const bool save_buttons,
const bool save_keypads,
const bool save_analogs,
const bool save_lights) {
auto path = get_templates_path();
auto path_tmp = path;
path_tmp.replace_extension(L"tmp");
tinyxml2::XMLDocument doc;
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) {
auto *decl = doc.NewDeclaration();
doc.InsertFirstChild(decl);
auto *root = doc.NewElement("templates");
doc.InsertEndChild(root);
}
auto *root = doc.RootElement();
if (!root) {
root = doc.NewElement("templates");
doc.InsertEndChild(root);
}
// remove existing template with same name and game
auto *existing = root->FirstChildElement("template");
while (existing) {
auto *next = existing->NextSiblingElement("template");
const char *ename = existing->Attribute("name");
const char *egame = existing->Attribute("game");
if (ename && egame &&
std::string(ename) == tmpl.name &&
std::string(egame) == tmpl.game_name) {
root->DeleteChild(existing);
}
existing = next;
}
auto *tmpl_el = doc.NewElement("template");
tmpl_el->SetAttribute("name", tmpl.name.c_str());
tmpl_el->SetAttribute("game", tmpl.game_name.c_str());
// buttons — skip unbound entries
auto *buttons_el = doc.NewElement("buttons");
if (save_buttons) {
for (auto &btn : tmpl.buttons) {
if (!btn.primary.is_unbound()) {
write_button_entry(doc, buttons_el, btn.name, btn.primary);
}
for (auto &alt : btn.alternatives) {
if (!alt.is_unbound()) {
write_button_entry(doc, buttons_el, btn.name, alt);
}
}
}
}
tmpl_el->InsertEndChild(buttons_el);
// keypad buttons — skip unbound entries
if (!tmpl.keypad_buttons.empty()) {
auto *kp_el = doc.NewElement("keypad_buttons");
if (save_keypads) {
for (auto &btn : tmpl.keypad_buttons) {
if (!btn.primary.is_unbound()) {
write_button_entry(doc, kp_el, btn.name, btn.primary);
}
for (auto &alt : btn.alternatives) {
if (!alt.is_unbound()) {
write_button_entry(doc, kp_el, btn.name, alt);
}
}
}
}
tmpl_el->InsertEndChild(kp_el);
}
// analogs — skip unbound entries
auto *analogs_el = doc.NewElement("analogs");
if (save_analogs) {
for (auto &a : tmpl.analogs) {
if (!a.is_unbound()) {
write_analog(doc, analogs_el, a);
}
}
}
tmpl_el->InsertEndChild(analogs_el);
// lights — skip unbound entries
auto *lights_el = doc.NewElement("lights");
if (save_lights) {
for (auto &l : tmpl.lights) {
if (!l.primary.is_unbound()) {
write_light_entry(doc, lights_el, l.name, l.primary);
}
for (auto &alt : l.alternatives) {
if (!alt.is_unbound()) {
write_light_entry(doc, lights_el, l.name, alt);
}
}
}
}
tmpl_el->InsertEndChild(lights_el);
root->InsertEndChild(tmpl_el);
// ensure directory exists
if (!path.parent_path().empty() && !std::filesystem::exists(path.parent_path())) {
fileutils::dir_create_recursive(path.parent_path());
}
// save atomically via temp file
auto save_err = doc.SaveFile(path_tmp.c_str());
if (save_err != tinyxml2::XML_SUCCESS) {
log_warning("templates", "failed to save templates file: {}", (int)save_err);
return false;
}
std::error_code ec;
std::filesystem::rename(path_tmp, path, ec);
if (ec) {
log_warning("templates", "failed to rename templates file: {}", ec.message());
return false;
}
return true;
}
bool delete_user_template(const std::string &game_name, const std::string &template_name) {
auto path = get_templates_path();
tinyxml2::XMLDocument doc;
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) return false;
auto *root = doc.RootElement();
if (!root) return false;
bool found = false;
auto *tmpl_el = root->FirstChildElement("template");
while (tmpl_el) {
auto *next = tmpl_el->NextSiblingElement("template");
const char *ename = tmpl_el->Attribute("name");
const char *egame = tmpl_el->Attribute("game");
if (ename && egame &&
std::string(ename) == template_name &&
std::string(egame) == game_name) {
root->DeleteChild(tmpl_el);
found = true;
}
tmpl_el = next;
}
if (found) {
auto path_tmp = path;
path_tmp.replace_extension(L"tmp");
if (doc.SaveFile(path_tmp.c_str()) == tinyxml2::XML_SUCCESS) {
std::error_code ec;
std::filesystem::rename(path_tmp, path, ec);
}
}
return found;
}
bool rename_user_template(const std::string &game_name,
const std::string &old_name, const std::string &new_name) {
auto path = get_templates_path();
tinyxml2::XMLDocument doc;
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) return false;
auto *root = doc.RootElement();
if (!root) return false;
bool found = false;
auto *tmpl_el = root->FirstChildElement("template");
while (tmpl_el) {
const char *ename = tmpl_el->Attribute("name");
const char *egame = tmpl_el->Attribute("game");
if (ename && egame &&
std::string(ename) == old_name &&
std::string(egame) == game_name) {
tmpl_el->SetAttribute("name", new_name.c_str());
found = true;
break;
}
tmpl_el = tmpl_el->NextSiblingElement("template");
}
if (found) {
auto path_tmp = path;
path_tmp.replace_extension(L"tmp");
if (doc.SaveFile(path_tmp.c_str()) == tinyxml2::XML_SUCCESS) {
std::error_code ec;
std::filesystem::rename(path_tmp, path, ec);
}
}
return found;
}
std::vector<ControllerTemplate> get_templates_for_game(const std::string &game_name) {
std::vector<ControllerTemplate> result;
log_misc("templates", "loading built-in templates for {}", game_name);
for (auto &t : load_builtin_templates()) {
if (t.game_name == game_name) {
result.push_back(std::move(t));
}
}
log_misc("templates", "loading user templates for {}", game_name);
for (auto &t : load_user_templates()) {
if (t.game_name == game_name) {
result.push_back(std::move(t));
}
}
return result;
}
}
+11
View File
@@ -11,6 +11,7 @@ class Light {
private:
std::vector<Light> alternatives;
std::string lightName;
std::string lightCategory;
std::string deviceIdentifier = "";
unsigned int index = 0;
bool is_temporary = false;
@@ -23,6 +24,8 @@ public:
float override_state = 0.f;
explicit Light(std::string lightName) : lightName(std::move(lightName)) {};
explicit Light(std::string lightName, std::string lightCategory) :
lightName(std::move(lightName)), lightCategory(std::move(lightCategory)) {};
std::string getDisplayString(rawinput::RawInputManager* manager);
@@ -51,6 +54,14 @@ public:
return this->lightName;
}
inline const std::string &getCategory() const {
return this->lightCategory;
}
inline void setCategory(std::string category) {
this->lightCategory = std::move(category);
}
inline const std::string &getDeviceIdentifier() const {
return this->deviceIdentifier;
}
+1
View File
@@ -4,3 +4,4 @@
#define IDR_PATCHES 132
#define IDR_README 133
#define IDR_DSEGFONT 134
#define IDR_CONTROLLER_PRESETS_BUILTIN 135
+114 -110
View File
@@ -41,136 +41,140 @@ std::vector<Light> &games::ddr::get_lights() {
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Dance Dance Revolution");
GameAPI::Lights::sortLights(
&lights,
"P1 Foot Left",
"P1 Foot Up",
"P1 Foot Right",
"P1 Foot Down",
"P2 Foot Left",
"P2 Foot Up",
"P2 Foot Right",
"P2 Foot Down",
"Spot Red",
"Spot Blue",
"Top Spot Red",
"Top Spot Blue",
"P1 Halogen Upper",
"P1 Halogen Lower",
"P2 Halogen Upper",
"P2 Halogen Lower",
"P1 Button",
"P2 Button",
"Neon",
"HD P1 Start",
"HD P1 Menu Left-Right",
"HD P1 Menu Up-Down",
"HD P2 Start",
"HD P2 Menu Left-Right",
"HD P2 Menu Up-Down",
"HD P1 Speaker F R",
"HD P1 Speaker F G",
"HD P1 Speaker F B",
"HD P1 Speaker W R",
"HD P1 Speaker W G",
"HD P1 Speaker W B",
"HD P2 Speaker F R",
"HD P2 Speaker F G",
"HD P2 Speaker F B",
"HD P2 Speaker W R",
"HD P2 Speaker W G",
"HD P2 Speaker W B",
GameAPI::Lights::sortLightsWithCategory(
&lights,
{
{"General", "P1 Foot Left"},
{"General", "P1 Foot Up"},
{"General", "P1 Foot Right"},
{"General", "P1 Foot Down"},
{"General", "P2 Foot Left"},
{"General", "P2 Foot Up"},
{"General", "P2 Foot Right"},
{"General", "P2 Foot Down"},
"WHITE Speaker Top R",
"WHITE Speaker Top G",
"WHITE Speaker Top B",
"WHITE Speaker Bottom R",
"WHITE Speaker Bottom G",
"WHITE Speaker Bottom B",
"WHITE Woofer R",
"WHITE Woofer G",
"WHITE Woofer B",
{"SD", "Spot Red"},
{"SD", "Spot Blue"},
{"SD", "Top Spot Red"},
{"SD", "Top Spot Blue"},
{"SD", "P1 Halogen Upper"},
{"SD", "P1 Halogen Lower"},
{"SD", "P2 Halogen Upper"},
{"SD", "P2 Halogen Lower"},
{"SD", "P1 Button"},
{"SD", "P2 Button"},
{"SD", "Neon"},
"GOLD P1 Menu Start",
"GOLD P1 Menu Up",
"GOLD P1 Menu Down",
"GOLD P1 Menu Left",
"GOLD P1 Menu Right",
{"HD", "HD P1 Start"},
{"HD", "HD P1 Menu Left-Right"},
{"HD", "HD P1 Menu Up-Down"},
{"HD", "HD P2 Start"},
{"HD", "HD P2 Menu Left-Right"},
{"HD", "HD P2 Menu Up-Down"},
{"HD", "HD P1 Speaker F R"},
{"HD", "HD P1 Speaker F G"},
{"HD", "HD P1 Speaker F B"},
{"HD", "HD P1 Speaker W R"},
{"HD", "HD P1 Speaker W G"},
{"HD", "HD P1 Speaker W B"},
{"HD", "HD P2 Speaker F R"},
{"HD", "HD P2 Speaker F G"},
{"HD", "HD P2 Speaker F B"},
{"HD", "HD P2 Speaker W R"},
{"HD", "HD P2 Speaker W G"},
{"HD", "HD P2 Speaker W B"},
"GOLD P2 Menu Start",
"GOLD P2 Menu Up",
"GOLD P2 Menu Down",
"GOLD P2 Menu Left",
"GOLD P2 Menu Right",
{"WHITE", "WHITE Speaker Top R"},
{"WHITE", "WHITE Speaker Top G"},
{"WHITE", "WHITE Speaker Top B"},
{"WHITE", "WHITE Speaker Bottom R"},
{"WHITE", "WHITE Speaker Bottom G"},
{"WHITE", "WHITE Speaker Bottom B"},
{"WHITE", "WHITE Woofer R"},
{"WHITE", "WHITE Woofer G"},
{"WHITE", "WHITE Woofer B"},
"GOLD Title Panel Left",
"GOLD Title Panel Center",
"GOLD Title Panel Right",
{"GOLD", "GOLD P1 Menu Start"},
{"GOLD", "GOLD P1 Menu Up"},
{"GOLD", "GOLD P1 Menu Down"},
{"GOLD", "GOLD P1 Menu Left"},
{"GOLD", "GOLD P1 Menu Right"},
"GOLD P1 Woofer Corner",
"GOLD P2 Woofer Corner",
{"GOLD", "GOLD P2 Menu Start"},
{"GOLD", "GOLD P2 Menu Up"},
{"GOLD", "GOLD P2 Menu Down"},
{"GOLD", "GOLD P2 Menu Left"},
{"GOLD", "GOLD P2 Menu Right"},
"GOLD P1 Card Unit R",
"GOLD P1 Card Unit G",
"GOLD P1 Card Unit B",
{"GOLD", "GOLD Title Panel Left"},
{"GOLD", "GOLD Title Panel Center"},
{"GOLD", "GOLD Title Panel Right"},
"GOLD P2 Card Unit R",
"GOLD P2 Card Unit G",
"GOLD P2 Card Unit B",
{"GOLD", "GOLD P1 Woofer Corner"},
{"GOLD", "GOLD P2 Woofer Corner"},
"GOLD Top Panel Avg R",
"GOLD Top Panel Avg G",
"GOLD Top Panel Avg B",
{"GOLD", "GOLD P1 Card Unit R"},
{"GOLD", "GOLD P1 Card Unit G"},
{"GOLD", "GOLD P1 Card Unit B"},
"GOLD Monitor Side Left Avg R",
"GOLD Monitor Side Left Avg G",
"GOLD Monitor Side Left Avg B",
{"GOLD", "GOLD P2 Card Unit R"},
{"GOLD", "GOLD P2 Card Unit G"},
{"GOLD", "GOLD P2 Card Unit B"},
"GOLD Monitor Side Right Avg R",
"GOLD Monitor Side Right Avg G",
"GOLD Monitor Side Right Avg B",
{"GOLD", "GOLD Top Panel Avg R"},
{"GOLD", "GOLD Top Panel Avg G"},
{"GOLD", "GOLD Top Panel Avg B"},
"GOLD P1 Foot Up Avg R",
"GOLD P1 Foot Up Avg G",
"GOLD P1 Foot Up Avg B",
{"GOLD", "GOLD Monitor Side Left Avg R"},
{"GOLD", "GOLD Monitor Side Left Avg G"},
{"GOLD", "GOLD Monitor Side Left Avg B"},
"GOLD P1 Foot Down Avg R",
"GOLD P1 Foot Down Avg G",
"GOLD P1 Foot Down Avg B",
{"GOLD", "GOLD Monitor Side Right Avg R"},
{"GOLD", "GOLD Monitor Side Right Avg G"},
{"GOLD", "GOLD Monitor Side Right Avg B"},
"GOLD P1 Foot Left Avg R",
"GOLD P1 Foot Left Avg G",
"GOLD P1 Foot Left Avg B",
{"GOLD", "GOLD P1 Foot Up Avg R"},
{"GOLD", "GOLD P1 Foot Up Avg G"},
{"GOLD", "GOLD P1 Foot Up Avg B"},
"GOLD P1 Foot Right Avg R",
"GOLD P1 Foot Right Avg G",
"GOLD P1 Foot Right Avg B",
{"GOLD", "GOLD P1 Foot Down Avg R"},
{"GOLD", "GOLD P1 Foot Down Avg G"},
{"GOLD", "GOLD P1 Foot Down Avg B"},
"GOLD P2 Foot Up Avg R",
"GOLD P2 Foot Up Avg G",
"GOLD P2 Foot Up Avg B",
{"GOLD", "GOLD P1 Foot Left Avg R"},
{"GOLD", "GOLD P1 Foot Left Avg G"},
{"GOLD", "GOLD P1 Foot Left Avg B"},
"GOLD P2 Foot Down Avg R",
"GOLD P2 Foot Down Avg G",
"GOLD P2 Foot Down Avg B",
{"GOLD", "GOLD P1 Foot Right Avg R"},
{"GOLD", "GOLD P1 Foot Right Avg G"},
{"GOLD", "GOLD P1 Foot Right Avg B"},
"GOLD P2 Foot Left Avg R",
"GOLD P2 Foot Left Avg G",
"GOLD P2 Foot Left Avg B",
{"GOLD", "GOLD P2 Foot Up Avg R"},
{"GOLD", "GOLD P2 Foot Up Avg G"},
{"GOLD", "GOLD P2 Foot Up Avg B"},
"GOLD P2 Foot Right Avg R",
"GOLD P2 Foot Right Avg G",
"GOLD P2 Foot Right Avg B",
{"GOLD", "GOLD P2 Foot Down Avg R"},
{"GOLD", "GOLD P2 Foot Down Avg G"},
{"GOLD", "GOLD P2 Foot Down Avg B"},
"GOLD P1 Stage Corner Up-Left",
"GOLD P1 Stage Corner Up-Right",
"GOLD P1 Stage Corner Down-Left",
"GOLD P1 Stage Corner Down-Right",
{"GOLD", "GOLD P2 Foot Left Avg R"},
{"GOLD", "GOLD P2 Foot Left Avg G"},
{"GOLD", "GOLD P2 Foot Left Avg B"},
"GOLD P2 Stage Corner Up-Left",
"GOLD P2 Stage Corner Up-Right",
"GOLD P2 Stage Corner Down-Left",
"GOLD P2 Stage Corner Down-Right"
{"GOLD", "GOLD P2 Foot Right Avg R"},
{"GOLD", "GOLD P2 Foot Right Avg G"},
{"GOLD", "GOLD P2 Foot Right Avg B"},
{"GOLD", "GOLD P1 Stage Corner Up-Left"},
{"GOLD", "GOLD P1 Stage Corner Up-Right"},
{"GOLD", "GOLD P1 Stage Corner Down-Left"},
{"GOLD", "GOLD P1 Stage Corner Down-Right"},
{"GOLD", "GOLD P2 Stage Corner Up-Left"},
{"GOLD", "GOLD P2 Stage Corner Up-Right"},
{"GOLD", "GOLD P2 Stage Corner Down-Left"},
{"GOLD", "GOLD P2 Stage Corner Down-Right"}
}
);
}
+1 -1
View File
@@ -282,7 +282,7 @@ int games::ddr::DDRP3IOHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWri
}
// use both sat spots for a neon pulse
float value_neon = (light_bits & hd_mapping_bits[0] && hd_mapping_bits[1]) ? 1.f : 0.f;
float value_neon = (light_bits & hd_mapping_bits[0] && light_bits & hd_mapping_bits[1]) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::NEON], value_neon);
}
+83 -81
View File
@@ -158,100 +158,102 @@ std::vector<Light> &games::gitadora::get_lights() {
if (lights.empty()) {
lights = GameAPI::Lights::getLights("GitaDora");
GameAPI::Lights::sortLights(&lights,
"Guitar P1 Motor",
"Guitar P2 Motor",
GameAPI::Lights::sortLightsWithCategory(&lights,
{
{"Guitar", "Guitar P1 Motor"},
{"Guitar", "Guitar P2 Motor"},
"P1 Start",
"P1 Menu Up Down (DX)",
"P1 Menu Left Right (DX)",
"P1 Help (DX)",
{"Menu", "P1 Start"},
{"Menu", "P1 Menu Up Down (DX)"},
{"Menu", "P1 Menu Left Right (DX)"},
{"Menu", "P1 Help (DX)"},
"P2 Start",
"P2 Menu Up Down (DX)",
"P2 Menu Left Right (DX)",
"P2 Help (DX)",
{"Menu", "P2 Start"},
{"Menu", "P2 Menu Up Down (DX)"},
{"Menu", "P2 Menu Left Right (DX)"},
{"Menu", "P2 Help (DX)"},
"Drum Left Cymbal",
"Drum Hi-Hat",
"Drum Snare",
"Drum High Tom",
"Drum Low Tom",
"Drum Floor Tom",
"Drum Right Cymbal",
{"Drums", "Drum Left Cymbal"},
{"Drums", "Drum Hi-Hat"},
{"Drums", "Drum Snare"},
{"Drums", "Drum High Tom"},
{"Drums", "Drum Low Tom"},
{"Drums", "Drum Floor Tom"},
{"Drums", "Drum Right Cymbal"},
"Drum Woofer R",
"Drum Woofer G",
"Drum Woofer B",
{"DX/SD", "Drum Woofer R"},
{"DX/SD", "Drum Woofer G"},
{"DX/SD", "Drum Woofer B"},
"Drum Stage R (DX)",
"Drum Stage G (DX)",
"Drum Stage B (DX)",
{"DX/SD", "Drum Stage R (DX)"},
{"DX/SD", "Drum Stage G (DX)"},
{"DX/SD", "Drum Stage B (DX)"},
"Spot Left (DX)",
"Spot Right (DX)",
"Spot Center Left (DX)",
"Spot Center Right (DX)",
{"DX/SD", "Spot Left (DX)"},
{"DX/SD", "Spot Right (DX)"},
{"DX/SD", "Spot Center Left (DX)"},
{"DX/SD", "Spot Center Right (DX)"},
"Drum Spot Rear Left (DX)",
"Drum Spot Rear Right (DX)",
{"DX/SD", "Drum Spot Rear Left (DX)"},
{"DX/SD", "Drum Spot Rear Right (DX)"},
"Guitar Lower Left R (DX)",
"Guitar Lower Left G (DX)",
"Guitar Lower Left B (DX)",
"Guitar Lower Right R (DX)",
"Guitar Lower Right G (DX)",
"Guitar Lower Right B (DX)",
{"DX/SD", "Guitar Lower Left R (DX)"},
{"DX/SD", "Guitar Lower Left G (DX)"},
{"DX/SD", "Guitar Lower Left B (DX)"},
{"DX/SD", "Guitar Lower Right R (DX)"},
{"DX/SD", "Guitar Lower Right G (DX)"},
{"DX/SD", "Guitar Lower Right B (DX)"},
"Guitar Left Speaker Upper R (DX)",
"Guitar Left Speaker Upper G (DX)",
"Guitar Left Speaker Upper B (DX)",
"Guitar Left Speaker Mid Up Left R (DX)",
"Guitar Left Speaker Mid Up Left G (DX)",
"Guitar Left Speaker Mid Up Left B (DX)",
"Guitar Left Speaker Mid Up Right R (DX)",
"Guitar Left Speaker Mid Up Right G (DX)",
"Guitar Left Speaker Mid Up Right B (DX)",
"Guitar Left Speaker Mid Low Left R (DX)",
"Guitar Left Speaker Mid Low Left G (DX)",
"Guitar Left Speaker Mid Low Left B (DX)",
"Guitar Left Speaker Mid Low Right R (DX)",
"Guitar Left Speaker Mid Low Right G (DX)",
"Guitar Left Speaker Mid Low Right B (DX)",
"Guitar Left Speaker Lower R (DX)",
"Guitar Left Speaker Lower G (DX)",
"Guitar Left Speaker Lower B (DX)",
{"DX/SD", "Guitar Left Speaker Upper R (DX)"},
{"DX/SD", "Guitar Left Speaker Upper G (DX)"},
{"DX/SD", "Guitar Left Speaker Upper B (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Up Left R (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Up Left G (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Up Left B (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Up Right R (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Up Right G (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Up Right B (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Low Left R (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Low Left G (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Low Left B (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Low Right R (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Low Right G (DX)"},
{"DX/SD", "Guitar Left Speaker Mid Low Right B (DX)"},
{"DX/SD", "Guitar Left Speaker Lower R (DX)"},
{"DX/SD", "Guitar Left Speaker Lower G (DX)"},
{"DX/SD", "Guitar Left Speaker Lower B (DX)"},
"Guitar Right Speaker Upper R (DX)",
"Guitar Right Speaker Upper G (DX)",
"Guitar Right Speaker Upper B (DX)",
"Guitar Right Speaker Mid Up Left R (DX)",
"Guitar Right Speaker Mid Up Left G (DX)",
"Guitar Right Speaker Mid Up Left B (DX)",
"Guitar Right Speaker Mid Up Right R (DX)",
"Guitar Right Speaker Mid Up Right G (DX)",
"Guitar Right Speaker Mid Up Right B (DX)",
"Guitar Right Speaker Mid Low Left R (DX)",
"Guitar Right Speaker Mid Low Left G (DX)",
"Guitar Right Speaker Mid Low Left B (DX)",
"Guitar Right Speaker Mid Low Right R (DX)",
"Guitar Right Speaker Mid Low Right G (DX)",
"Guitar Right Speaker Mid Low Right B (DX)",
"Guitar Right Speaker Lower R (DX)",
"Guitar Right Speaker Lower G (DX)",
"Guitar Right Speaker Lower B (DX)",
{"DX/SD", "Guitar Right Speaker Upper R (DX)"},
{"DX/SD", "Guitar Right Speaker Upper G (DX)"},
{"DX/SD", "Guitar Right Speaker Upper B (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Up Left R (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Up Left G (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Up Left B (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Up Right R (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Up Right G (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Up Right B (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Low Left R (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Low Left G (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Low Left B (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Low Right R (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Low Right G (DX)"},
{"DX/SD", "Guitar Right Speaker Mid Low Right B (DX)"},
{"DX/SD", "Guitar Right Speaker Lower R (DX)"},
{"DX/SD", "Guitar Right Speaker Lower G (DX)"},
{"DX/SD", "Guitar Right Speaker Lower B (DX)"},
"Title Average R (Arena)",
"Title Average G (Arena)",
"Title Average B (Arena)",
{"Arena Model", "Title Average R (Arena)"},
{"Arena Model", "Title Average G (Arena)"},
{"Arena Model", "Title Average B (Arena)"},
"Woofer R (Arena)",
"Woofer G (Arena)",
"Woofer B (Arena)",
{"Arena Model", "Woofer R (Arena)"},
{"Arena Model", "Woofer G (Arena)"},
{"Arena Model", "Woofer B (Arena)"},
"Card Reader R (Arena)",
"Card Reader G (Arena)",
"Card Reader B (Arena)"
{"Arena Model", "Card Reader R (Arena)"},
{"Arena Model", "Card Reader G (Arena)"},
{"Arena Model", "Card Reader B (Arena)"}
}
);
}
+5 -5
View File
@@ -473,7 +473,7 @@ namespace games::iidx {
"!!! please do the following instead: !!!\n"
"!!! !!!\n"
"!!! Revert your changes to XML file so it says !!!\n"
"!!! <model __type=\"str\">LDJ</model> !!!\n"
"!!! <model __type=\"str\">LDJ</model> !!!\n"
"!!! !!!\n"
"!!! In SpiceCfg, enable 'IIDX TDJ Mode' or provide -iidxtdj flag !!!\n"
"!!! in command line !!!\n"
@@ -818,7 +818,7 @@ namespace games::iidx {
}
bool is_tdj_fhd() {
return TDJ_MODE && avs::game::is_ext(2022101900, MAXINT);
return TDJ_MODE && avs::game::is_ext(2022101900, INT_MAX);
}
void apply_audio_hacks() {
@@ -884,12 +884,12 @@ namespace games::iidx {
// patch iidx32+ for asio compatibility
// only do this if NOT wasapi (as opposed to checking if it's asio)
// the patch is only really needed for (some) non-XONAR devices but since people sometimes disguise
// the patch is only really needed for (some) non-XONAR devices but since people sometimes disguise
// other devices as a XONAR, don't check for the exact string (common ASIO workaround for INF)
if (avs::game::is_ext(2024090100, MAXINT) &&
if (avs::game::is_ext(2024090100, INT_MAX) &&
!(SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() &&
SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi")) {
// in iidx32 final:
// ff 50 08 call QWORD PTR [rax+0x8] ; ASIO instance AddRef
// 48 8b 4b 08 mov rcx,QWORD PTR [rbx+0x8]
+98 -96
View File
@@ -83,103 +83,105 @@ std::vector<Light> &games::iidx::get_lights() {
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Beatmania IIDX");
GameAPI::Lights::sortLights(
GameAPI::Lights::sortLightsWithCategory(
&lights,
"P1 1",
"P1 2",
"P1 3",
"P1 4",
"P1 5",
"P1 6",
"P1 7",
"P2 1",
"P2 2",
"P2 3",
"P2 4",
"P2 5",
"P2 6",
"P2 7",
"P1 Start",
"P2 Start",
"VEFX",
"Effect",
"Spot Light 1",
"Spot Light 2",
"Spot Light 3",
"Spot Light 4",
"Spot Light 5",
"Spot Light 6",
"Spot Light 7",
"Spot Light 8",
"Neon Lamp",
"Woofer R",
"Woofer G",
"Woofer B",
"IC Card Reader P1 R",
"IC Card Reader P1 G",
"IC Card Reader P1 B",
"IC Card Reader P2 R",
"IC Card Reader P2 G",
"IC Card Reader P2 B",
"Turntable P1 R",
"Turntable P1 G",
"Turntable P1 B",
"Turntable P2 R",
"Turntable P2 G",
"Turntable P2 B",
"Turntable P1 Resistance",
"Turntable P2 Resistance",
"Stage Left Avg R",
"Stage Left Avg G",
"Stage Left Avg B",
"Stage Right Avg R",
"Stage Right Avg G",
"Stage Right Avg B",
"Cabinet Left Avg R",
"Cabinet Left Avg G",
"Cabinet Left Avg B",
"Cabinet Right Avg R",
"Cabinet Right Avg G",
"Cabinet Right Avg B",
"Control Panel Under Avg R",
"Control Panel Under Avg G",
"Control Panel Under Avg B",
"Ceiling Left Avg R",
"Ceiling Left Avg G",
"Ceiling Left Avg B",
"Title Left Avg R",
"Title Left Avg G",
"Title Left Avg B",
"Title Right Avg R",
"Title Right Avg G",
"Title Right Avg B",
"Ceiling Right Avg R",
"Ceiling Right Avg G",
"Ceiling Right Avg B",
"Touch Panel Left Avg R",
"Touch Panel Left Avg G",
"Touch Panel Left Avg B",
"Touch Panel Right Avg R",
"Touch Panel Right Avg G",
"Touch Panel Right Avg B",
"Side Panel Left Inner Avg R",
"Side Panel Left Inner Avg G",
"Side Panel Left Inner Avg B",
"Side Panel Left Outer Avg R",
"Side Panel Left Outer Avg G",
"Side Panel Left Outer Avg B",
"Side Panel Left Avg R",
"Side Panel Left Avg G",
"Side Panel Left Avg B",
"Side Panel Right Outer Avg R",
"Side Panel Right Outer Avg G",
"Side Panel Right Outer Avg B",
"Side Panel Right Inner Avg R",
"Side Panel Right Inner Avg G",
"Side Panel Right Inner Avg B",
"Side Panel Right Avg R",
"Side Panel Right Avg G",
"Side Panel Right Avg B"
{
{"Common", "P1 1"},
{"Common", "P1 2"},
{"Common", "P1 3"},
{"Common", "P1 4"},
{"Common", "P1 5"},
{"Common", "P1 6"},
{"Common", "P1 7"},
{"Common", "P2 1"},
{"Common", "P2 2"},
{"Common", "P2 3"},
{"Common", "P2 4"},
{"Common", "P2 5"},
{"Common", "P2 6"},
{"Common", "P2 7"},
{"Common", "P1 Start"},
{"Common", "P2 Start"},
{"Common", "VEFX"},
{"Common", "Effect"},
{"DX", "Spot Light 1"},
{"DX", "Spot Light 2"},
{"DX", "Spot Light 3"},
{"DX", "Spot Light 4"},
{"DX", "Spot Light 5"},
{"DX", "Spot Light 6"},
{"DX", "Spot Light 7"},
{"DX", "Spot Light 8"},
{"DX", "Neon Lamp"},
{"Lightning Model", "Woofer R"},
{"Lightning Model", "Woofer G"},
{"Lightning Model", "Woofer B"},
{"Lightning Model", "IC Card Reader P1 R"},
{"Lightning Model", "IC Card Reader P1 G"},
{"Lightning Model", "IC Card Reader P1 B"},
{"Lightning Model", "IC Card Reader P2 R"},
{"Lightning Model", "IC Card Reader P2 G"},
{"Lightning Model", "IC Card Reader P2 B"},
{"Lightning Model", "Turntable P1 R"},
{"Lightning Model", "Turntable P1 G"},
{"Lightning Model", "Turntable P1 B"},
{"Lightning Model", "Turntable P2 R"},
{"Lightning Model", "Turntable P2 G"},
{"Lightning Model", "Turntable P2 B"},
{"Lightning Model", "Turntable P1 Resistance"},
{"Lightning Model", "Turntable P2 Resistance"},
{"Lightning Model", "Stage Left Avg R"},
{"Lightning Model", "Stage Left Avg G"},
{"Lightning Model", "Stage Left Avg B"},
{"Lightning Model", "Stage Right Avg R"},
{"Lightning Model", "Stage Right Avg G"},
{"Lightning Model", "Stage Right Avg B"},
{"Lightning Model", "Cabinet Left Avg R"},
{"Lightning Model", "Cabinet Left Avg G"},
{"Lightning Model", "Cabinet Left Avg B"},
{"Lightning Model", "Cabinet Right Avg R"},
{"Lightning Model", "Cabinet Right Avg G"},
{"Lightning Model", "Cabinet Right Avg B"},
{"Lightning Model", "Control Panel Under Avg R"},
{"Lightning Model", "Control Panel Under Avg G"},
{"Lightning Model", "Control Panel Under Avg B"},
{"Lightning Model", "Ceiling Left Avg R"},
{"Lightning Model", "Ceiling Left Avg G"},
{"Lightning Model", "Ceiling Left Avg B"},
{"Lightning Model", "Title Left Avg R"},
{"Lightning Model", "Title Left Avg G"},
{"Lightning Model", "Title Left Avg B"},
{"Lightning Model", "Title Right Avg R"},
{"Lightning Model", "Title Right Avg G"},
{"Lightning Model", "Title Right Avg B"},
{"Lightning Model", "Ceiling Right Avg R"},
{"Lightning Model", "Ceiling Right Avg G"},
{"Lightning Model", "Ceiling Right Avg B"},
{"Lightning Model", "Touch Panel Left Avg R"},
{"Lightning Model", "Touch Panel Left Avg G"},
{"Lightning Model", "Touch Panel Left Avg B"},
{"Lightning Model", "Touch Panel Right Avg R"},
{"Lightning Model", "Touch Panel Right Avg G"},
{"Lightning Model", "Touch Panel Right Avg B"},
{"Lightning Model", "Side Panel Left Inner Avg R"},
{"Lightning Model", "Side Panel Left Inner Avg G"},
{"Lightning Model", "Side Panel Left Inner Avg B"},
{"Lightning Model", "Side Panel Left Outer Avg R"},
{"Lightning Model", "Side Panel Left Outer Avg G"},
{"Lightning Model", "Side Panel Left Outer Avg B"},
{"Lightning Model", "Side Panel Left Avg R"},
{"Lightning Model", "Side Panel Left Avg G"},
{"Lightning Model", "Side Panel Left Avg B"},
{"Lightning Model", "Side Panel Right Outer Avg R"},
{"Lightning Model", "Side Panel Right Outer Avg G"},
{"Lightning Model", "Side Panel Right Outer Avg B"},
{"Lightning Model", "Side Panel Right Inner Avg R"},
{"Lightning Model", "Side Panel Right Inner Avg G"},
{"Lightning Model", "Side Panel Right Inner Avg B"},
{"Lightning Model", "Side Panel Right Avg R"},
{"Lightning Model", "Side Panel Right Avg G"},
{"Lightning Model", "Side Panel Right Avg B"}
}
);
}
+1 -1
View File
@@ -89,7 +89,7 @@ namespace games::iidx {
protected:
virtual ~IIDXLocalCamera() {};
ULONG m_nRefCount;
LONG m_nRefCount;
CRITICAL_SECTION m_critsec;
std::string m_name;
+3 -1
View File
@@ -256,6 +256,7 @@ namespace games {
const std::string otoca("Otoca D'or");
games.push_back(otoca);
buttons.insert({ otoca, otoca::get_buttons() });
buttons_help.insert({ otoca, otoca::get_buttons_help() });
file_hints[otoca].emplace_back("arkkep.dll");
// winning eleven
@@ -400,7 +401,8 @@ namespace games {
vkey_defaults.push_back(unit == 0 ? VK_NUMPAD9 : 0xFF);
names.emplace_back(prefix + "00");
vkey_defaults.push_back(unit == 0 ? VK_RETURN : 0xFF);
// this used to be VK_RETURN, but that shares a value with normal Enter key
vkey_defaults.push_back(unit == 0 ? VK_SUBTRACT : 0xFF);
names.emplace_back(prefix + "Decimal");
vkey_defaults.push_back(unit == 0 ? VK_DECIMAL : 0xFF);
+5
View File
@@ -23,6 +23,11 @@ std::vector<Button> &games::otoca::get_buttons() {
return buttons;
}
std::string games::otoca::get_buttons_help() {
// keep to max 100 characters wide
return " BT_Left Lever BT_Right";
}
std::vector<Light> &games::otoca::get_lights() {
static std::vector<Light> lights;
+1
View File
@@ -30,5 +30,6 @@ namespace games::otoca {
// getters
std::vector<Button> &get_buttons();
std::string get_buttons_help();
std::vector<Light> &get_lights();
}
+142
View File
@@ -1,16 +1,121 @@
#include "otoca.h"
#include "p4io.h"
#include "games/shared/printer.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#define OTOCA_DEBUG_VERBOSE 0
#if OTOCA_DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
namespace games::otoca {
bool BYPASS_CAMERA = false;
// for dev only
static bool CAMLIB_CALL_ORIGINAL = false;
OtocaGame::OtocaGame() : Game("Otoca D'or") {
}
OtocaGame::~OtocaGame() {
}
typedef void (__cdecl *LibCameraInit_t)();
static LibCameraInit_t LibCameraInit_orig = nullptr;
static void __cdecl LibCameraInit_hook() {
if (CAMLIB_CALL_ORIGINAL) {
LibCameraInit_orig();
log_debug("otoca::cam", "LibCameraInit returned");
}
}
typedef int (__cdecl *LibCameraOpen_t)(double, int, int);
static LibCameraOpen_t LibCameraOpen_orig = nullptr;
static int __cdecl LibCameraOpen_hook(double a1, int a2, int a3) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam, (15, 2, 0) returned 0
ret = LibCameraOpen_orig(a1, a2, a3);
log_debug("otoca::cam", "LibCameraOpen({}, {}, {}) returned {}", a1, a2, a3, ret);
}
return ret;
}
typedef int (__cdecl *LibCameraStop_t)(int);
static LibCameraStop_t LibCameraStop_orig = nullptr;
static int __cdecl LibCameraStop_hook(int a1) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam,
ret = LibCameraStop_orig(a1);
log_debug("otoca::cam", "LibCameraStop({}) returned {}", a1, ret);
}
return ret;
}
typedef void (__cdecl *LibCameraGetCameraNr_t)();
static LibCameraGetCameraNr_t LibCameraGetCameraNr_orig = nullptr;
static void __cdecl LibCameraGetCameraNr_hook() {
if (CAMLIB_CALL_ORIGINAL) {
LibCameraGetCameraNr_orig();
log_debug("otoca::cam", "LibCameraGetCameraNr returned");
}
}
typedef int (__cdecl *LibCameraRun_t)(int);
static LibCameraRun_t LibCameraRun_orig = nullptr;
static int __cdecl LibCameraRun_hook(int a1) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam, (0) returned 0
ret = LibCameraRun_orig(a1);
log_debug("otoca::cam", "LibCameraRun({}) returned {}", a1, ret);
}
return ret;
}
typedef int (__cdecl *LibCameraGetImage_t)(int, void *);
static LibCameraGetImage_t LibCameraGetImage_orig = nullptr;
static int __cdecl LibCameraGetImage_hook(int a1, void *a2) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// not sure if this is called
ret = LibCameraGetImage_orig(a1, a2);
log_debug("otoca::cam", "LibCameraGetImage({}, {}) returned {}", a1, fmt::ptr(a2), ret);
}
return ret;
}
typedef int (__cdecl *LibCameraGetBrightness_t)(int *, int);
static LibCameraGetBrightness_t LibCameraGetBrightness_orig = nullptr;
static int __cdecl LibCameraGetBrightness_hook(int *a1, int a2) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam, (ptr,) returned
ret = LibCameraGetBrightness_orig(a1, a2);
}
log_debug("otoca::cam", "LibCameraGetBrightness({}, {}) returned {}", fmt::ptr(a1), a2, ret);
return ret;
}
typedef int (__cdecl *LibCameraGetBrightnessRange_t)(int *, int *, int);
static LibCameraGetBrightnessRange_t LibCameraGetBrightnessRange_orig = nullptr;
static int __cdecl LibCameraGetBrightnessRange_hook(int *a1, int *a2, int a3) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam, (ptr, ptr, 0) returned 0
ret = LibCameraGetBrightnessRange_orig(a1, a2, a3);
log_debug("otoca::cam", "LibCameraGetBrightnessRange({}, {}, {}) returned {}", fmt::ptr(a1), fmt::ptr(a2), a3, ret);
}
return ret;
}
void OtocaGame::attach() {
Game::attach();
@@ -23,5 +128,42 @@ namespace games::otoca {
// initialize IO hooks
p4io_hook();
games::shared::printer_attach();
if (BYPASS_CAMERA) {
libutils::try_library("libcamera.dll");
const auto libcamera = "libcamera.dll";
detour::trampoline_try(
libcamera,
"?LibCameraInit@@YAXXZ",
LibCameraInit_hook, &LibCameraInit_orig);
detour::trampoline_try(
libcamera,
"?LibCameraOpen@@YA?AW4LIBCAMERA_STATUS@@NHPAUt_libcamera_open_param@@@Z",
LibCameraOpen_hook, &LibCameraOpen_orig);
detour::trampoline_try(
libcamera,
"?LibCameraStop@@YA?AW4LIBCAMERA_STATUS@@H@Z",
LibCameraStop_hook, &LibCameraStop_orig);
detour::trampoline_try(
libcamera,
"?LibCameraGetCameraNr@@YAHXZ",
LibCameraGetCameraNr_hook, &LibCameraGetCameraNr_orig);
detour::trampoline_try(
libcamera,
"?LibCameraRun@@YA?AW4LIBCAMERA_STATUS@@H@Z",
LibCameraRun_hook, &LibCameraRun_orig);
detour::trampoline_try(
libcamera,
"?LibCameraGetImage@@YA?AW4LIBCAMERA_STATUS@@HPAX@Z",
LibCameraGetImage_hook, &LibCameraGetImage_orig);
detour::trampoline_try(
libcamera,
"?LibCameraGetBrightness@@YA?AW4LIBCAMERA_STATUS@@PAJH@Z",
LibCameraGetBrightness_hook, &LibCameraGetBrightness_orig);
detour::trampoline_try(
libcamera,
"?LibCameraGetBrightnessRange@@YA?AW4LIBCAMERA_STATUS@@PAJ0H@Z",
LibCameraGetBrightnessRange_hook, &LibCameraGetBrightnessRange_orig);
}
}
}
+2
View File
@@ -4,6 +4,8 @@
namespace games::otoca {
extern bool BYPASS_CAMERA;
class OtocaGame : public games::Game {
public:
OtocaGame();
+8 -5
View File
@@ -1,3 +1,6 @@
// QueryDisplayConfig
#define _WIN32_WINNT 0x0601
#include "pc.h"
#include <format>
@@ -32,10 +35,10 @@ namespace games::pc {
if (pRawInputDevices &&
(uiNumDevices > 0) &&
(pRawInputDevices[0].hwndTarget == RI_MGR->input_hwnd)) {
return RegisterRawInputDevices_orig(pRawInputDevices, uiNumDevices, cbSize);
}
// otherwise, it must be the game; prevent the game from registering for raw input
// and hijacking WM_INPUT messages.
SetLastError(0xDEADBEEF);
@@ -58,7 +61,7 @@ namespace games::pc {
}
LONG WINAPI QueryDisplayConfig_hook(UINT32, UINT32*, DISPLAYCONFIG_PATH_INFO*, UINT32*, DISPLAYCONFIG_MODE_INFO*, DISPLAYCONFIG_TOPOLOGY_ID*) {
// make unity fallback to EnumDisplaySettingsW as I don't
// make unity fallback to EnumDisplaySettingsW as I don't
// want to deal with this api which is way more complex
return ERROR_NOT_SUPPORTED;
}
@@ -98,9 +101,9 @@ namespace games::pc {
const auto user32Dll = "user32.dll";
detour::trampoline_try(user32Dll, "RegisterRawInputDevices",
RegisterRawInputDevices_hook, &RegisterRawInputDevices_orig);
detour::trampoline_try(user32Dll, "QueryDisplayConfig",
detour::trampoline_try(user32Dll, "QueryDisplayConfig",
QueryDisplayConfig_hook, &QueryDisplayConfig_orig);
detour::trampoline_try(user32Dll, "EnumDisplaySettingsW",
detour::trampoline_try(user32Dll, "EnumDisplaySettingsW",
EnumDisplaySettingsW_hook, &EnumDisplaySettingsW_orig);
if (GRAPHICS_SHOW_CURSOR) {
+1 -1
View File
@@ -88,10 +88,10 @@ namespace games::qma {
// create quiz directory
static bool dirs_created = false;
if (!dirs_created && _wcsnicmp(lpFileName, L"E:\\LMA", 6) == 0) {
dirs_created = true;
fileutils::dir_create_recursive("E:\\LMA\\quiz11");
fileutils::dir_create_recursive("E:\\LMA\\quiz13");
fileutils::dir_create_recursive("E:\\LMA\\quiz15");
dirs_created = true;
}
// return result
+71 -69
View File
@@ -65,76 +65,78 @@ std::vector<Light> &games::sdvx::get_lights() {
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Sound Voltex");
GameAPI::Lights::sortLights(
GameAPI::Lights::sortLightsWithCategory(
&lights,
"BT-A",
"BT-B",
"BT-C",
"BT-D",
"FX-L",
"FX-R",
"Start",
"Wing Left Up R",
"Wing Left Up G",
"Wing Left Up B",
"Wing Right Up R",
"Wing Right Up G",
"Wing Right Up B",
"Wing Left Low R",
"Wing Left Low G",
"Wing Left Low B",
"Wing Right Low R",
"Wing Right Low G",
"Wing Right Low B",
"Woofer R",
"Woofer G",
"Woofer B",
"Controller R",
"Controller G",
"Controller B",
"Generator R",
"Generator G",
"Generator B",
"Pop",
"Title Left",
"Title Right",
"Volume Sound",
"Volume Headphone",
"Volume External",
"Volume Woofer",
"IC Card Reader R",
"IC Card Reader G",
"IC Card Reader B",
"Title Avg R",
"Title Avg G",
"Title Avg B",
"Upper Left Speaker Avg R",
"Upper Left Speaker Avg G",
"Upper Left Speaker Avg B",
"Upper Right Speaker Avg R",
"Upper Right Speaker Avg G",
"Upper Right Speaker Avg B",
"Left Wing Avg R",
"Left Wing Avg G",
"Left Wing Avg B",
"Right Wing Avg R",
"Right Wing Avg G",
"Right Wing Avg B",
"Lower Left Speaker Avg R",
"Lower Left Speaker Avg G",
"Lower Left Speaker Avg B",
"Lower Right Speaker Avg R",
"Lower Right Speaker Avg G",
"Lower Right Speaker Avg B",
"Control Panel Avg R",
"Control Panel Avg G",
"Control Panel Avg B",
"Woofer Avg R",
"Woofer Avg G",
"Woofer Avg B",
"V Unit Avg R",
"V Unit Avg G",
"V Unit Avg B"
{
{"Common", "BT-A"},
{"Common", "BT-B"},
{"Common", "BT-C"},
{"Common", "BT-D"},
{"Common", "FX-L"},
{"Common", "FX-R"},
{"Common", "Start"},
{"Nemsys", "Wing Left Up R"},
{"Nemsys", "Wing Left Up G"},
{"Nemsys", "Wing Left Up B"},
{"Nemsys", "Wing Right Up R"},
{"Nemsys", "Wing Right Up G"},
{"Nemsys", "Wing Right Up B"},
{"Nemsys", "Wing Left Low R"},
{"Nemsys", "Wing Left Low G"},
{"Nemsys", "Wing Left Low B"},
{"Nemsys", "Wing Right Low R"},
{"Nemsys", "Wing Right Low G"},
{"Nemsys", "Wing Right Low B"},
{"Nemsys", "Woofer R"},
{"Nemsys", "Woofer G"},
{"Nemsys", "Woofer B"},
{"Nemsys", "Controller R"},
{"Nemsys", "Controller G"},
{"Nemsys", "Controller B"},
{"Nemsys", "Generator R"},
{"Nemsys", "Generator G"},
{"Nemsys", "Generator B"},
{"Nemsys", "Pop"},
{"Nemsys", "Title Left"},
{"Nemsys", "Title Right"},
{"System", "Volume Sound"},
{"System", "Volume Headphone"},
{"System", "Volume External"},
{"System", "Volume Woofer"},
{"Valkyrie", "IC Card Reader R"},
{"Valkyrie", "IC Card Reader G"},
{"Valkyrie", "IC Card Reader B"},
{"Valkyrie", "Title Avg R"},
{"Valkyrie", "Title Avg G"},
{"Valkyrie", "Title Avg B"},
{"Valkyrie", "Upper Left Speaker Avg R"},
{"Valkyrie", "Upper Left Speaker Avg G"},
{"Valkyrie", "Upper Left Speaker Avg B"},
{"Valkyrie", "Upper Right Speaker Avg R"},
{"Valkyrie", "Upper Right Speaker Avg G"},
{"Valkyrie", "Upper Right Speaker Avg B"},
{"Valkyrie", "Left Wing Avg R"},
{"Valkyrie", "Left Wing Avg G"},
{"Valkyrie", "Left Wing Avg B"},
{"Valkyrie", "Right Wing Avg R"},
{"Valkyrie", "Right Wing Avg G"},
{"Valkyrie", "Right Wing Avg B"},
{"Valkyrie", "Lower Left Speaker Avg R"},
{"Valkyrie", "Lower Left Speaker Avg G"},
{"Valkyrie", "Lower Left Speaker Avg B"},
{"Valkyrie", "Lower Right Speaker Avg R"},
{"Valkyrie", "Lower Right Speaker Avg G"},
{"Valkyrie", "Lower Right Speaker Avg B"},
{"Valkyrie", "Control Panel Avg R"},
{"Valkyrie", "Control Panel Avg G"},
{"Valkyrie", "Control Panel Avg B"},
{"Valkyrie", "Woofer Avg R"},
{"Valkyrie", "Woofer Avg G"},
{"Valkyrie", "Woofer Avg B"},
{"Valkyrie", "V Unit Avg R"},
{"Valkyrie", "V Unit Avg G"},
{"Valkyrie", "V Unit Avg B"}
}
);
}
+21
View File
@@ -234,6 +234,27 @@ namespace games::sdvx {
});
return false;
}
static bool logged_missing_layer_error = false;
// this is raw SHIFT-JIS text for
// W:BM2D: CreateLayer() 指定したレイヤーは存在しません
if (!logged_missing_layer_error &&
data.find(
"\x57\x3A\x42\x4D\x32\x44\x3A\x20\x43\x72\x65\x61\x74\x65\x4C\x61\x79\x65\x72\x28\x29\x20"
"\x8E\x77\x92\xE8\x82\xB5\x82\xBD\x83\x8C\x83\x43\x83\x84\x81\x5B\x82\xCD\x91\xB6\x8D\xDD"
"\x82\xB5\x82\xDC\x82\xB9\x82\xF1") != std::string::npos) {
logged_missing_layer_error = true;
deferredlogs::defer_error_messages({
"sdvx game engine detected missing or corrupt file(s); raw SHIFT-JIS error was:",
std::string(" ") + data,
" this will almost certainly cause your game to crash",
" this is often caused by mismatched game DLLs or missing resources from game updates",
" double check the integrity of your game data and try again"
});
return false;
}
return false;
}
+1 -1
View File
@@ -334,7 +334,7 @@ namespace games::shared {
image_data[index + 2] = tmp;
}
if (avs::game::is_model({"KLP", "KFC"})) {
if (avs::game::is_model({"KLP", "KFC", "NCG"})) {
// rotate image clockwise
log_misc("printer", "rotate clockwise...");
auto rotated = new uint8_t[image_width * image_height * 3];
@@ -278,7 +278,7 @@ static bool is_dx9_on_12_enabled() {
}
if (GRAPHICS_9_ON_12_STATE == DX9ON12_FORCE_ON) {
if (avs::game::is_model("LDJ") && avs::game::is_ext(2023091500, MAXINT)) {
if (avs::game::is_model("LDJ") && avs::game::is_ext(2023091500, INT_MAX)) {
deferredlogs::defer_error_messages({
"dx9on12 was force enabled by user (-dx9on12)",
" IIDX31+ is known to be incompatible with DX 9on12, leading to blank screen or crashes",
@@ -1570,4 +1570,4 @@ void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params) {
params->BackBufferHeight);
return;
}
}
}
@@ -593,7 +593,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::UpdateTexture(
WRAP_DEBUG;
if (CUSTOM_RESET) {
#ifdef __GNUC__
#if defined(__GNUC__) && !defined(__clang__)
// Do a pointer compare rather than `QueryInterface` because it is a lot cheaper than incrementing
// the reference count
@@ -768,7 +768,7 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
pReal->ColorFill(topSurface, nullptr, D3DCOLOR_XRGB(0, 0, 0));
cfg::SCREENRESIZE->need_surface_clean = false;
}
D3DLOCKED_RECT rect;
HRESULT hr;
topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
+1
View File
@@ -1052,6 +1052,7 @@ bool graphics_capture_receive_jpeg(int screen, TooJpeg::WRITE_ONE_BYTE receiver,
auto capture_width = capture.width;
auto capture_height = capture.height;
auto capture_timestamp = capture.timestamp;
capture.data = nullptr;
lock.unlock();
// validate data
+3
View File
@@ -1,3 +1,6 @@
// GetLocaleInfoEx
#define _WIN32_WINNT 0x0600
#include "lang.h"
#define WIN32_NO_STATUS
+3
View File
@@ -1,3 +1,6 @@
// DisplayConfigSetDeviceInfo
#define _WIN32_WINNT 0x0601
#include "winuser.h"
#include "util/detour.h"
+6
View File
@@ -559,6 +559,10 @@ int main_implementation(int argc, char *argv[]) {
nvenc_hook::FORCE_DISABLE = true;
}
#endif
if (options[launcher::Options::OtocaCamHook].value_bool()) {
games::otoca::BYPASS_CAMERA = true;
}
if (options[launcher::Options::LoadJubeatModule].value_bool()) {
attach_jb = true;
}
@@ -1503,6 +1507,7 @@ int main_implementation(int argc, char *argv[]) {
// early hooks
for (auto &hook : early_hooks) {
log_info("launcher", "loading early hook DLL {}", hook);
libutils::print_dll_info(hook);
HMODULE module;
if (!(module = libutils::try_library(hook))) {
log_warning("launcher", "failed to load early hook {}: {}", hook, get_last_error_string());
@@ -2303,6 +2308,7 @@ int main_implementation(int argc, char *argv[]) {
// load hooks
for (auto &hook : game_hooks) {
log_info("launcher", "loading hook DLL {}", hook);
libutils::print_dll_info(hook);
HMODULE module;
if (!(module = libutils::try_library(hook))) {
log_warning("launcher", "failed to load hook {}: {}", hook, get_last_error_string());
+13 -2
View File
@@ -1532,9 +1532,9 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Touch Parameters",
},
{
.title = "Invert Raw Input Touch",
.title = "Invert Touch",
.name = "touchinvert",
.desc = "Inverts raw touch coordinates; only works for default raw input handler, "
.desc = "Inverts touch coordinates; only works for default raw input handler and IIDX/SDVX native touch handler, "
"and not Windows Touch API.",
.type = OptionType::Bool,
.category = "Touch Parameters",
@@ -2678,6 +2678,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool,
.category = "Development",
.disabled = true,
},
{
// OtocaCamHook
.title = "Otoca Camera Check Bypass",
.name = "otocacamhook",
.desc =
"This game REQUIRES a (compatible) webcam to save progress. If you don't have any, check this "
"option to bypass camera error during boot, allowing you to try out the game.",
.type = OptionType::Bool,
.game_name = "Otoca D'or",
.category = "Game Options",
}
};
+1
View File
@@ -271,6 +271,7 @@ namespace launcher {
LovePlusPrinterOutputFormat,
LovePlusPrinterJPGQuality,
OptionConflictResolution,
OtocaCamHook,
};
enum class OptionsCategory {
+4
View File
@@ -92,6 +92,10 @@ namespace nativetouchhook {
}
}
if (rawinput::touch::INVERTED) {
flip_values = !flip_values;
}
for (size_t i = 0; i < cInputs; i++) {
PTOUCHINPUT point = &pInputs[i];
+13
View File
@@ -163,4 +163,17 @@ namespace ImGui {
ImGui::PopID();
return clicked;
}
void InvisibleTableRowSelectable() {
ImGui::TableSetColumnIndex(0);
ImGui::PushStyleColor(ImGuiCol_Header, 0);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, 0);
ImGui::PushStyleColor(ImGuiCol_HeaderActive, 0);
ImGui::Selectable("##row", false,
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered()) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(200, 200, 200, 24));
}
}
}
+1
View File
@@ -20,4 +20,5 @@ namespace ImGui {
void TextTruncated(const std::string& p_text, float p_truncated_width);
bool DeleteButton(const std::string& tooltip);
bool ClearButton(const std::string& tooltip);
void InvisibleTableRowSelectable();
}
+30 -2
View File
@@ -11,6 +11,7 @@
#include "rawinput/rawinput.h"
#include "touch/touch.h"
#include "util/logging.h"
#include "util/time.h"
#include "util/utils.h"
#include "hooks/graphics/graphics.h"
@@ -29,6 +30,8 @@ static HWND g_hWnd = nullptr;
static INT64 g_Time = 0;
static INT64 g_TicksPerSecond = 0;
static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT;
static double g_LastMouseMovement = 0.f;
static bool g_MouseCursorAutoHide = false;
bool ImGui_ImplSpice_Init(HWND hWnd) {
log_misc("imgui_impl_spice", "init");
@@ -83,6 +86,12 @@ bool ImGui_ImplSpice_Init(HWND hWnd) {
// get display size
ImGui_ImplSpice_UpdateDisplaySize();
// only enable mouse cursor auto hiding if we want ImGui to draw the cursor
// (if OS is drawing it, we don't auto-hide)
if (io.MouseDrawCursor && !cfg::CONFIGURATOR_STANDALONE) {
g_MouseCursorAutoHide = true;
}
// return success
return true;
}
@@ -375,12 +384,31 @@ void ImGui_ImplSpice_NewFrame() {
}
// set mouse wheel
auto mouse_diff = mouse_wheel - mouse_wheel_last;
auto wheel_diff = mouse_wheel - mouse_wheel_last;
mouse_wheel_last = mouse_wheel;
io.MouseWheel = mouse_diff;
io.MouseWheel = wheel_diff;
// update OS mouse position
const auto old_mouse_pos = io.MousePos;
ImGui_ImplSpice_UpdateMousePos();
const auto new_mouse_pos = io.MousePos;
// automatically hide cursor
if (g_MouseCursorAutoHide) {
if (old_mouse_pos.x != new_mouse_pos.x ||
old_mouse_pos.y != new_mouse_pos.y ||
wheel_diff != 0 ||
io.MouseDown[0] || io.MouseDown[1] || io.MouseDown[2]) {
// mouse moved, update time and show the cursor
g_LastMouseMovement = get_performance_milliseconds();
io.MouseDrawCursor = true;
} else if ((get_performance_milliseconds() - g_LastMouseMovement) > 2000) {
// mouse idle for more than 2 seconds, hide the cursor
io.MouseDrawCursor = false;
}
}
if (cfg::CONFIGURATOR_STANDALONE) {
// if cursor is inside the client area, always set the OS cursor to what ImGui wants
+58 -56
View File
@@ -188,71 +188,73 @@ void overlay::SpiceOverlay::init() {
ImGui::GetIO().FontGlobalScale = scale;
}
// red theme based on:
// https://github.com/ocornut/imgui/issues/707#issuecomment-760220280
// r, g, b, a
// based on CrimsonVesuvius theme from
// https://github.com/ocornut/imgui/issues/707#issuecomment-4107169777
ImVec4* colors = ImGui::GetStyle().Colors;
// note: also see bg_alpha value in each overlay window
colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 1.0f);
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 1.0f);
// Text
colors[ImGuiCol_Text] = ImVec4(1.00f, 0.90f, 0.90f, 1.00f); // Slight pinkish tint to off-white
colors[ImGuiCol_TextDisabled] = ImVec4(0.35f, 0.26f, 0.26f, 1.00f);
colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.00f, 0.50f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
// Backgrounds
colors[ImGuiCol_WindowBg] = ImVec4(0.08f, 0.07f, 0.07f, 1.00f); // Deep charcoal
colors[ImGuiCol_ChildBg] = ImVec4(0.10f, 0.09f, 0.09f, 1.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.07f, 0.07f, 0.96f);
colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.18f, 0.18f, 0.54f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.18f, 0.18f, 0.18f, 0.80f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.24f, 0.24f, 0.24f, 0.80f);
// Borders
colors[ImGuiCol_Border] = ImVec4(0.25f, 0.15f, 0.15f, 0.80f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.48f, 0.16f, 0.16f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.48f, 0.16f, 0.16f, 1.00f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.89f, 0.00f, 0.19f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 0.19f, 0.19f, 0.40f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.89f, 0.00f, 0.19f, 1.00f);
// Frames
colors[ImGuiCol_FrameBg] = ImVec4(0.15f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.25f, 0.15f, 0.15f, 1.00f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.35f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(1.00f, 0.19f, 0.19f, 0.40f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.80f, 0.17f, 0.00f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.80f, 0.17f, 0.00f, 0.90f);
// Title Bars
colors[ImGuiCol_TitleBg] = ImVec4(0.12f, 0.08f, 0.08f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.25f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.05f, 0.05f, 0.05f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.33f, 0.35f, 0.36f, 0.53f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.67f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.47f, 0.47f, 0.47f, 0.67f);
colors[ImGuiCol_Separator] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.85f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(1.00f, 1.00f, 1.00f, 0.60f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.90f);
// Menus
colors[ImGuiCol_MenuBarBg] = ImVec4(0.12f, 0.08f, 0.08f, 1.00f);
colors[ImGuiCol_Tab] = colors[ImGuiCol_Button];
colors[ImGuiCol_TabHovered] = colors[ImGuiCol_ButtonHovered];
colors[ImGuiCol_TabActive] = colors[ImGuiCol_ButtonActive];
colors[ImGuiCol_TabUnfocused] = colors[ImGuiCol_Tab] * ImVec4(1.0f, 1.0f, 1.0f, 0.6f);
colors[ImGuiCol_TabUnfocusedActive] = colors[ImGuiCol_TabActive] * ImVec4(1.0f, 1.0f, 1.0f, 0.6f);
// Scrollbars
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.05f, 0.05f, 0.05f, 1.00f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.25f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.35f, 0.15f, 0.15f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.45f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_DockingPreview] = ImVec4(0.47f, 0.47f, 0.47f, 0.47f);
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f);
colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f);
colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f);
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.02f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
// Interactables (The High-Intensity Red)
colors[ImGuiCol_CheckMark] = ImVec4(0.85f, 0.15f, 0.15f, 1.00f); // Sharp Red
colors[ImGuiCol_SliderGrab] = ImVec4(0.60f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.85f, 0.15f, 0.15f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.30f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.50f, 0.18f, 0.18f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.70f, 0.25f, 0.25f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.30f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.50f, 0.18f, 0.18f, 1.00f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.70f, 0.25f, 0.25f, 1.00f);
// Tabs
colors[ImGuiCol_Tab] = ImVec4(0.15f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_TabHovered] = ImVec4(0.50f, 0.18f, 0.18f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.35f, 0.12f, 0.12f, 1.00f);
// Misc
colors[ImGuiCol_PlotLines] = ImVec4(0.85f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.85f, 0.15f, 0.15f, 0.35f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.85f, 0.15f, 0.15f, 1.00f);
// spice custom
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.025f);
colors[ImGuiCol_Separator] = ImVec4(0.32f, 0.22f, 0.22f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.42f, 0.22f, 0.22f, 1.00f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.52f, 0.22f, 0.22f, 1.00f);
#ifdef IMGUI_HAS_DOCK
colors[ImGuiCol_DockingPreview] = ImVec4(0.85f, 0.15f, 0.15f, 0.40f);
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.08f, 0.07f, 0.07f, 1.00f);
#endif
// configure IO
auto &io = ImGui::GetIO();
File diff suppressed because it is too large Load Diff
+81 -2
View File
@@ -1,5 +1,6 @@
#pragma once
#include <chrono>
#include <optional>
#include "cfg/game.h"
@@ -7,6 +8,7 @@
#include "rawinput/device.h"
#include "external/imgui/imgui_filebrowser.h"
#include "patch_manager.h"
#include "controller_presets.h"
namespace overlay::windows {
@@ -22,9 +24,18 @@ namespace overlay::windows {
CONFIG_TAB_OPTIONS,
CONFIG_TAB_ADVANCED,
CONFIG_TAB_DEV,
CONFIG_TAB_PRESETS,
CONFIG_TAB_SEARCH,
};
struct MatchEntry {
std::string game_name;
std::string device_name;
int light_index;
int control_index;
bool soft;
};
class Config : public Window {
private:
@@ -58,6 +69,24 @@ namespace overlay::windows {
int lights_devices_selected = -1;
int lights_devices_control_selected = -1;
// lights test all
bool lights_testing = false;
int lights_test_current = -1;
std::chrono::steady_clock::time_point lights_test_time;
// lights auto match
std::vector<rawinput::Device *> auto_match_devices;
int auto_match_device_selected = -1;
bool auto_match_testing = false;
int auto_match_test_current = -1;
std::chrono::steady_clock::time_point auto_match_test_time;
std::string auto_match_test_device;
unsigned int auto_match_test_control = 0;
std::chrono::steady_clock::time_point auto_match_copy_time;
bool auto_match_copied = false;
bool auto_match_soft_enabled = true;
bool auto_match_p2 = false;
// keypads tab
int keypads_selected[2] {};
char keypads_card_path[2][1024] {};
@@ -65,6 +94,34 @@ namespace overlay::windows {
bool keypads_card_select_done = false;
ImGui::FileBrowser keypads_card_select_browser[2];
char keypads_card_number[2][18] {};
bool keypads_card_override_valid[2] = { false, false };
bool keypads_card_file_contents_valid[2] = { false, false };
// presets tab
std::vector<ControllerTemplate> templates_cache;
bool templates_cache_dirty = true;
int templates_selected = -1;
char template_save_name[256] = {};
// per-source target mapping: source index -> selected target device index
std::vector<int> template_target_selection;
// deferred popup flags (to avoid ImGui ID stack mismatch inside tables)
bool template_apply_open = false;
bool template_edit_open = false;
bool template_save_labels_open = false;
bool template_delete_open = false;
ControllerTemplate template_pending_save;
std::vector<std::string> template_save_sources;
std::vector<std::string> template_save_labels;
bool apply_buttons = true;
bool apply_keypads = true;
bool apply_analogs = true;
bool apply_lights = true;
bool save_buttons = true;
bool save_keypads = true;
bool save_analogs = true;
bool save_lights = true;
bool all_cleared = false;
std::vector<bool> template_is_applied;
// patches tab
std::unique_ptr<PatchManager> patch_manager;
@@ -80,6 +137,8 @@ namespace overlay::windows {
std::thread *file_picker_thread = nullptr;
bool file_picker_done = false;
std::pair<std::string, int> analog_as_button_warning_show_next_frame = { "", 0 };
void build_buttons(const std::string &name, std::vector<Button> *buttons, int min = 0, int max = -1);
void build_button(
const std::string &name,
@@ -97,15 +156,34 @@ namespace overlay::windows {
Button *button,
const float button_velocity,
const int alt_index);
void clear_button(Button *button, const int alt_index);
void clear_button(Button *button, const int alt_index, std::optional<unsigned short> vKey_default = std::nullopt);
void reset_button_to_default(Button *button, unsigned short vKey_default);
unsigned int get_keypad_top_row(const Button &button);
void build_analogs(const std::string &name, std::vector<Analog> *analogs);
void edit_analog_popup(Analog &analog);
void update() override;
void stop_lights_test();
void build_lights(const std::string &name, std::vector<Light> *lights);
void build_light(Light &primary_light, Light *light, const int light_index, const int alt_index);
void clear_light(Light *light, const int alt_index);
void edit_light_popup(Light &primary_light, Light *light, const int alt_index);
void auto_match_lights_popup(std::vector<Light> *lights);
std::string match_lights(
rawinput::Device *device,
std::vector<Light> *lights,
std::vector<std::string> &raw_names,
std::vector<MatchEntry> &matched,
std::vector<MatchEntry> &unmatched);
void build_presets();
void clear_all_bindings();
void apply_template_source(const ControllerTemplate &tmpl,
const std::string &source_filter,
const std::string &target_device);
ControllerTemplate capture_current_bindings(const std::string &name);
void build_cards();
std::string build_option_value_picker_title(const OptionDefinition& option);
@@ -121,12 +199,13 @@ namespace overlay::windows {
void set_alternating_row_colors(const int row_index);
bool validate_ea_card(char card_number[16]);
public:
Config(SpiceOverlay *overlay);
~Config() override;
void read_card(int player = -1);
void write_card(int player);
void build_content() override;
};
@@ -0,0 +1,242 @@
#include "controller_presets.h"
#include <algorithm>
#include "cfg/button.h"
namespace overlay::windows {
// helpers - iterate both buttons and keypad_buttons
static void count_buttons_in(const std::vector<TemplateButtonBinding> &btns, int &naive, int &device) {
for (auto &btn : btns) {
if (btn.primary.is_naive()) naive++;
if (btn.primary.is_device()) device++;
for (auto &alt : btn.alternatives) {
if (alt.is_naive()) naive++;
if (alt.is_device()) device++;
}
}
}
int ControllerTemplate::count_naive_buttons() const {
int naive = 0, device = 0;
count_buttons_in(buttons, naive, device);
count_buttons_in(keypad_buttons, naive, device);
return naive;
}
int ControllerTemplate::count_device_buttons() const {
int naive = 0, device = 0;
count_buttons_in(buttons, naive, device);
count_buttons_in(keypad_buttons, naive, device);
return device;
}
int ControllerTemplate::count_device_analogs() const {
int count = 0;
for (auto &a : analogs) {
if (a.is_device()) count++;
}
return count;
}
int ControllerTemplate::count_device_lights() const {
int count = 0;
for (auto &l : lights) {
if (l.primary.is_device()) count++;
for (auto &alt : l.alternatives) {
if (alt.is_device()) count++;
}
}
return count;
}
static void collect_devices_from(const std::vector<TemplateButtonBinding> &btns,
std::set<std::string> &devices) {
for (auto &btn : btns) {
if (btn.primary.is_device()) devices.insert(btn.primary.device_identifier);
for (auto &alt : btn.alternatives) {
if (alt.is_device()) devices.insert(alt.device_identifier);
}
}
}
std::set<std::string> ControllerTemplate::get_used_devices() const {
std::set<std::string> devices;
collect_devices_from(buttons, devices);
collect_devices_from(keypad_buttons, devices);
for (auto &a : analogs) {
if (a.is_device()) devices.insert(a.device_identifier);
}
for (auto &l : lights) {
if (l.primary.is_device()) devices.insert(l.primary.device_identifier);
for (auto &alt : l.alternatives) {
if (alt.is_device()) devices.insert(alt.device_identifier);
}
}
return devices;
}
static void collect_sources_from(const std::vector<TemplateButtonBinding> &btns,
std::set<std::string> &sources_set, bool &has_naive) {
for (auto &btn : btns) {
if (btn.primary.is_naive()) has_naive = true;
if (btn.primary.is_device()) sources_set.insert(btn.primary.device_identifier);
for (auto &alt : btn.alternatives) {
if (alt.is_naive()) has_naive = true;
if (alt.is_device()) sources_set.insert(alt.device_identifier);
}
}
}
std::vector<std::string> ControllerTemplate::get_binding_sources() const {
std::set<std::string> sources_set;
bool has_naive = false;
collect_sources_from(buttons, sources_set, has_naive);
collect_sources_from(keypad_buttons, sources_set, has_naive);
for (auto &a : analogs) {
if (a.is_device()) {
sources_set.insert(a.device_identifier);
}
}
for (auto &l : lights) {
if (l.primary.is_device()) sources_set.insert(l.primary.device_identifier);
for (auto &alt : l.alternatives) {
if (alt.is_device()) sources_set.insert(alt.device_identifier);
}
}
std::vector<std::string> result;
if (has_naive) {
result.emplace_back("Naive");
}
for (auto &s : sources_set) {
result.push_back(s);
}
// sort alphabetically
std::sort(result.begin(), result.end());
return result;
}
void ControllerTemplate::rename_source(const std::string &old_id, const std::string &new_id) {
auto rename_in_buttons = [&](std::vector<TemplateButtonBinding> &btns) {
for (auto &btn : btns) {
if (btn.primary.device_identifier == old_id) btn.primary.device_identifier = new_id;
for (auto &alt : btn.alternatives) {
if (alt.device_identifier == old_id) alt.device_identifier = new_id;
}
}
};
rename_in_buttons(buttons);
rename_in_buttons(keypad_buttons);
for (auto &a : analogs) {
if (a.device_identifier == old_id) a.device_identifier = new_id;
}
for (auto &l : lights) {
if (l.primary.device_identifier == old_id) l.primary.device_identifier = new_id;
for (auto &alt : l.alternatives) {
if (alt.device_identifier == old_id) alt.device_identifier = new_id;
}
}
}
std::string ControllerTemplate::get_source_summary(const std::string &source) const {
bool is_naive = (source == "Naive");
std::string result;
// collect button lines from a button vector
auto collect_btn_lines = [&](const std::vector<TemplateButtonBinding> &btns,
std::vector<std::string> &lines) {
for (auto &tb : btns) {
auto check_entry = [&](const TemplateButtonEntry &e) {
if (is_naive && e.is_naive()) {
Button b("");
b.setVKey(e.vKey);
lines.push_back(tb.name + " = " + (e.vKey == INVALID_VKEY ? "None" : b.getVKeyString()));
} else if (!is_naive && e.is_device() && e.device_identifier == source) {
std::string desc = std::to_string(e.vKey);
if (e.analog_type != BAT_NONE) {
desc += " (analog:" + std::to_string((int)e.analog_type) + ")";
}
lines.push_back(tb.name + " = " + desc);
}
};
check_entry(tb.primary);
for (auto &alt : tb.alternatives) {
check_entry(alt);
}
}
};
// buttons
std::vector<std::string> btn_lines;
collect_btn_lines(buttons, btn_lines);
if (!btn_lines.empty()) {
result += "Buttons:\n";
for (auto &line : btn_lines) {
result += " " + line + "\n";
}
}
// keypad buttons
std::vector<std::string> kp_lines;
collect_btn_lines(keypad_buttons, kp_lines);
if (!kp_lines.empty()) {
result += "Keypad:\n";
for (auto &line : kp_lines) {
result += " " + line + "\n";
}
}
// analogs
if (!is_naive) {
std::vector<std::string> analog_lines;
for (auto &a : analogs) {
if (a.is_device() && a.device_identifier == source) {
analog_lines.push_back(a.name + " (idx:" + std::to_string(a.index) + ")");
}
}
if (!analog_lines.empty()) {
result += "Analogs:\n";
for (auto &line : analog_lines) {
result += " " + line + "\n";
}
}
}
// lights
if (!is_naive) {
std::vector<std::string> light_lines;
for (auto &l : lights) {
auto check_light = [&](const TemplateLightEntry &e) {
if (e.is_device() && e.device_identifier == source) {
light_lines.push_back(l.name + " (idx:" + std::to_string(e.index) + ")");
}
};
check_light(l.primary);
for (auto &alt : l.alternatives) {
check_light(alt);
}
}
if (!light_lines.empty()) {
result += "Lights:\n";
for (auto &line : light_lines) {
result += " " + line + "\n";
}
}
}
if (result.empty()) {
result = "(no bindings)";
}
// trim trailing newline
while (!result.empty() && result.back() == '\n') {
result.pop_back();
}
return result;
}
}
@@ -0,0 +1,158 @@
#pragma once
#include <set>
#include <string>
#include <vector>
#include "cfg/analog.h"
#include "cfg/button.h"
namespace overlay::windows {
// single button binding entry (primary or alternative)
struct TemplateButtonEntry {
unsigned short vKey = INVALID_VKEY;
ButtonAnalogType analog_type = BAT_NONE;
std::string device_identifier;
bool invert = false;
double debounce_up = 0.0;
double debounce_down = 0.0;
unsigned short velocity_threshold = 0;
bool is_naive() const { return device_identifier.empty() && vKey != INVALID_VKEY; }
bool is_device() const { return !device_identifier.empty(); }
bool is_unbound() const { return device_identifier.empty() && vKey == INVALID_VKEY; }
static TemplateButtonEntry FromButton(const Button &btn) {
TemplateButtonEntry e;
e.vKey = btn.getVKey();
e.analog_type = btn.getAnalogType();
e.device_identifier = btn.getDeviceIdentifier();
e.invert = btn.getInvert();
e.debounce_up = btn.getDebounceUp();
e.debounce_down = btn.getDebounceDown();
e.velocity_threshold = btn.getVelocityThreshold();
return e;
}
};
// button binding with primary + alternatives
struct TemplateButtonBinding {
std::string name;
TemplateButtonEntry primary;
std::vector<TemplateButtonEntry> alternatives;
TemplateButtonBinding() = default;
TemplateButtonBinding(std::string name, TemplateButtonEntry primary,
std::vector<TemplateButtonEntry> alternatives)
: name(std::move(name)), primary(std::move(primary)),
alternatives(std::move(alternatives)) {}
explicit TemplateButtonBinding(Button &btn) {
name = btn.getName();
primary = TemplateButtonEntry::FromButton(btn);
for (auto &alt : btn.getAlternatives()) {
alternatives.push_back(TemplateButtonEntry::FromButton(alt));
}
}
};
// analog binding
struct TemplateAnalogBinding {
std::string name;
std::string device_identifier;
unsigned short index = 0xFF;
float sensitivity = 1.f;
float deadzone = 0.f;
bool deadzone_mirror = false;
bool invert = false;
bool smoothing = false;
int multiplier = 1;
bool relative_mode = false;
int delay_buffer_depth = 0;
bool is_device() const { return !device_identifier.empty(); }
bool is_unbound() const { return device_identifier.empty() && index == 0xFF; }
TemplateAnalogBinding() = default;
explicit TemplateAnalogBinding(Analog &a) {
name = a.getName();
device_identifier = a.getDeviceIdentifier();
index = a.getIndex();
sensitivity = a.getSensitivity();
deadzone = a.getDeadzone();
deadzone_mirror = a.getDeadzoneMirror();
invert = a.getInvert();
smoothing = a.getSmoothing();
multiplier = a.getMultiplier();
relative_mode = a.isRelativeMode();
delay_buffer_depth = a.getDelayBufferDepth();
}
};
// single light binding entry (primary or alternative)
struct TemplateLightEntry {
std::string device_identifier;
unsigned int index = 0;
bool is_device() const { return !device_identifier.empty(); }
bool is_unbound() const { return device_identifier.empty(); }
};
// light binding with primary + alternatives
struct TemplateLightBinding {
std::string name;
TemplateLightEntry primary;
std::vector<TemplateLightEntry> alternatives;
TemplateLightBinding() = default;
TemplateLightBinding(std::string name, TemplateLightEntry primary,
std::vector<TemplateLightEntry> alternatives)
: name(std::move(name)), primary(std::move(primary)),
alternatives(std::move(alternatives)) {}
};
// controller preset
struct ControllerTemplate {
std::string name;
std::string game_name;
bool is_builtin = false;
std::vector<TemplateButtonBinding> buttons;
std::vector<TemplateButtonBinding> keypad_buttons;
std::vector<TemplateAnalogBinding> analogs;
std::vector<TemplateLightBinding> lights;
// helpers
int count_naive_buttons() const;
int count_device_buttons() const;
int count_device_analogs() const;
int count_device_lights() const;
std::set<std::string> get_used_devices() const;
// get all binding sources (device_identifier strings used in this preset)
std::vector<std::string> get_binding_sources() const;
// get summary of bindings for a specific source (for tooltip display)
std::string get_source_summary(const std::string &source) const;
// replace all occurrences of a source device_identifier with a new one
void rename_source(const std::string &old_id, const std::string &new_id);
};
// template management API
std::vector<ControllerTemplate> get_templates_for_game(const std::string &game_name);
std::vector<ControllerTemplate> load_user_templates();
bool save_user_template(
const ControllerTemplate &tmpl,
const bool save_buttons,
const bool save_keypads,
const bool save_analogs,
const bool save_lights);
bool delete_user_template(const std::string &game_name, const std::string &template_name);
bool rename_user_template(const std::string &game_name,
const std::string &old_name, const std::string &new_name);
}
+1
View File
@@ -2,6 +2,7 @@
#include "overlay/window.h"
#include "cfg/button.h"
#include "cfg/light.h"
namespace overlay::windows {
@@ -0,0 +1,82 @@
#pragma once
#include <cstdlib>
namespace overlay::windows {
struct LightMatchMap {
const char *controller; // controller name for display
const char *game; // game name to match
unsigned short vid; // USB vendor ID filter, or 0 to skip
unsigned short pid; // USB product ID filter, or 0 to skip
const char *device_light; // light name (or prefix if rgb)
const char *device_suffix; // after R/G/B for rgb mode, or ""
const char *address; // control index for address mode, or ""
bool rgb; // expand " R/G/B" between device_light and device_suffix
const char *game_light; // target game light (or prefix if rgb), without P1/P2 prefix
const char *game_light_alt; // alt game light for Valkyrie mode, or ""
};
static const LightMatchMap LIGHT_MATCH_MAP[] = {
// FAUCETWO - Sound Voltex (RGB)
{"FAUCETWO", "Sound Voltex", 0, 0, "Top LED", " 6/24", "", true, "Wing Left Up", "Upper Left Speaker Avg"},
{"FAUCETWO", "Sound Voltex", 0, 0, "Top LED", " 12/24", "", true, "Wing Left Low", "Lower Left Speaker Avg"},
{"FAUCETWO", "Sound Voltex", 0, 0, "Top LED", " 18/24", "", true, "Wing Right Low", "Lower Right Speaker Avg"},
{"FAUCETWO", "Sound Voltex", 0, 0, "Top LED", " 24/24", "", true, "Wing Right Up", "Upper Right Speaker Avg"},
{"FAUCETWO", "Sound Voltex", 0, 0, "Left and Right", "", "", true, "Controller", "Control Panel Avg"},
// FAUCETWO Portable - Sound Voltex (RGB)
{"FAUCETWO Portable", "Sound Voltex", 0, 0, "Light of Left Side", "", "", true, "Wing Left Up", "Left Wing Avg"},
{"FAUCETWO Portable", "Sound Voltex", 0, 0, "Light of Right Side", "", "", true, "Wing Right Up", "Right Wing Avg"},
{"FAUCETWO Portable", "Sound Voltex", 0, 0, "Light of Center1", "", "", true, "Controller", "Control Panel Avg"},
{"FAUCETWO Portable", "Sound Voltex", 0, 0, "Light of Center2", "", "", true, "Woofer", "Woofer Avg"},
// PHOENIXWAN - Beatmania IIDX (address based, VID:PID 034C:0368)
{"PHOENIXWAN", "Beatmania IIDX", 0x034C, 0x0368, "Generic Indicator", "", "0x00", false, "1", ""},
{"PHOENIXWAN", "Beatmania IIDX", 0x034C, 0x0368, "Generic Indicator", "", "0x01", false, "2", ""},
{"PHOENIXWAN", "Beatmania IIDX", 0x034C, 0x0368, "Generic Indicator", "", "0x02", false, "3", ""},
{"PHOENIXWAN", "Beatmania IIDX", 0x034C, 0x0368, "Generic Indicator", "", "0x03", false, "4", ""},
{"PHOENIXWAN", "Beatmania IIDX", 0x034C, 0x0368, "Generic Indicator", "", "0x04", false, "5", ""},
{"PHOENIXWAN", "Beatmania IIDX", 0x034C, 0x0368, "Generic Indicator", "", "0x05", false, "6", ""},
{"PHOENIXWAN", "Beatmania IIDX", 0x034C, 0x0368, "Generic Indicator", "", "0x06", false, "7", ""},
// Nostroller VID_0E8F&PID_1212&MI_03 (Lights 01-14) (RGB)
// The lights are named "LED_01R" "LED_01G" "LED_01B" and so on.
{"Nostroller (Lights 01-14)", "Nostalgia", 0, 0, "LED_01", "", "", true, "Key 1", ""},
{"Nostroller (Lights 01-14)", "Nostalgia", 0, 0, "LED_02", "", "", true, "Key 2", ""},
{"Nostroller (Lights 01-14)", "Nostalgia", 0, 0, "LED_03", "", "", true, "Key 3", ""},
{"Nostroller (Lights 01-14)", "Nostalgia", 0, 0, "LED_04", "", "", true, "Key 4", ""},
{"Nostroller (Lights 01-14)", "Nostalgia", 0, 0, "LED_05", "", "", true, "Key 5", ""},
{"Nostroller (Lights 01-14)", "Nostalgia", 0, 0, "LED_06", "", "", true, "Key 6", ""},
{"Nostroller (Lights 01-14)", "Nostalgia", 0, 0, "LED_07", "", "", true, "Key 7", ""},
{"Nostroller (Lights 01-14)", "Nostalgia", 0, 0, "LED_08", "", "", true, "Key 8", ""},
{"Nostroller (Lights 01-14)", "Nostalgia", 0, 0, "LED_09", "", "", true, "Key 9", ""},
{"Nostroller (Lights 01-14)", "Nostalgia", 0, 0, "LED_10", "", "", true, "Key 10", ""},
{"Nostroller (Lights 01-14)", "Nostalgia", 0, 0, "LED_11", "", "", true, "Key 11", ""},
{"Nostroller (Lights 01-14)", "Nostalgia", 0, 0, "LED_12", "", "", true, "Key 12", ""},
{"Nostroller (Lights 01-14)", "Nostalgia", 0, 0, "LED_13", "", "", true, "Key 13", ""},
{"Nostroller (Lights 01-14)", "Nostalgia", 0, 0, "LED_14", "", "", true, "Key 14", ""},
// Nostroller VID_0E8F&PID_1212&MI_02 (Lights 15-28) (RGB)
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_15", "", "", true, "Key 15", ""},
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_16", "", "", true, "Key 16", ""},
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_17", "", "", true, "Key 17", ""},
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_18", "", "", true, "Key 18", ""},
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_19", "", "", true, "Key 19", ""},
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_20", "", "", true, "Key 20", ""},
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_21", "", "", true, "Key 21", ""},
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_22", "", "", true, "Key 22", ""},
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_23", "", "", true, "Key 23", ""},
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_24", "", "", true, "Key 24", ""},
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_25", "", "", true, "Key 25", ""},
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_26", "", "", true, "Key 26", ""},
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_27", "", "", true, "Key 27", ""},
{"Nostroller (Lights 15-28)", "Nostalgia", 0, 0, "LED_28", "", "", true, "Key 28", ""},
};
static const int LIGHT_MATCH_MAP_COUNT =
sizeof(LIGHT_MATCH_MAP) / sizeof(LIGHT_MATCH_MAP[0]);
}
+29 -15
View File
@@ -150,9 +150,17 @@ namespace overlay::windows {
const auto gle = GetLastError();
log_warning("patchmanager", "WinHttpSendRequest failed: {}", gle);
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";
if (gle == ERROR_WINHTTP_SECURE_FAILURE) {
url_fetch_errors += "\nERROR_WINHTTP_SECURE_FAILURE\n\n";
url_fetch_errors += "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.\n";
} else if (gle == ERROR_WINHTTP_NAME_NOT_RESOLVED) {
url_fetch_errors += "\nERROR_WINHTTP_NAME_NOT_RESOLVED\n\n";
url_fetch_errors += "The server name could not be resolved.\n\n";
url_fetch_errors += "Website is offline, or your internet connection is down.";
} else if (gle == ERROR_WINHTTP_TIMEOUT) {
url_fetch_errors += "\nERROR_WINHTTP_TIMEOUT\n\n";
url_fetch_errors += "The request timed out.";
}
return result;
}
@@ -235,9 +243,9 @@ namespace overlay::windows {
#ifdef _MSC_VER
static_cast<PLDR_DLL_NOTIFICATION_FUNCTION>([]
#else
[] CALLBACK
[]
#endif
(ULONG reason, PCLDR_DLL_NOTIFICATION_DATA data, PVOID ctx) {
(ULONG reason, PCLDR_DLL_NOTIFICATION_DATA data, PVOID ctx) CALLBACK {
if (reason == LDR_DLL_NOTIFICATION_REASON_LOADED) {
auto const dll = strtolower(std::filesystem::path({
data->Loaded.FullDllName->Buffer,
@@ -432,7 +440,7 @@ namespace overlay::windows {
}
}
}
bool url_entered = false;
bool is_valid_url = false;
bool patches_imported = false;
@@ -459,7 +467,7 @@ namespace overlay::windows {
&patch_url,
ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_AutoSelectAll);
if (ImGui::Button("Paste")) {
if (ImGui::Button("Paste from clipboard")) {
auto clipboard_url = clipboard::paste_text();
if (!clipboard_url.empty()) {
strreplace(clipboard_url, "\r\n", "");
@@ -534,7 +542,9 @@ namespace overlay::windows {
}
this->config_save();
}
reload_local_patches();
// import_remote_patches_to_disk clears out old patches, so regardless of result,
// reload patches from disk
reload_local_patches(true);
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
@@ -635,7 +645,7 @@ namespace overlay::windows {
const auto search_str_in_lower = strtolower(patch_name_filter);
size_t patches_shown = 0;
for (auto &patch : patches) {
// get patch status
PatchStatus patch_status = is_patch_active(patch);
patch.last_status = patch_status;
@@ -834,9 +844,13 @@ namespace overlay::windows {
ImGui::TextUnformatted(patch_checked ? "ON" : "off");
ImGui::EndDisabled();
}
// row hover detection (invisible selectable that spans entire row)
ImGui::InvisibleTableRowSelectable();
ImGui::PopID();
}
if (patches_shown == 0) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
@@ -981,7 +995,7 @@ namespace overlay::windows {
"patchmanager",
"matched auto apply entry by full game identifier: {}",
entry_id);
} else if (is_game_id_wildcard_matched(entry_id)) {
// match on model and ext, ignoring dest/spec/rev
// sample: LDJ:J:E:A:2025011400
@@ -1208,7 +1222,7 @@ namespace overlay::windows {
identifier,
time);
}
return identifier;
}
@@ -2000,7 +2014,7 @@ namespace overlay::windows {
};
// get data offset
memory_patch_data.data_offset =
memory_patch_data.data_offset =
parse_json_data_offset(patch_data.name, memory_patch);
if (memory_patch_data.data_offset == 0) {
continue;
@@ -2155,7 +2169,7 @@ namespace overlay::windows {
}
// get offset
uint64_t union_offset =
uint64_t union_offset =
parse_json_data_offset(patch_data.name, union_patch_it->value);
if (union_offset == 0) {
continue;
@@ -2193,7 +2207,7 @@ namespace overlay::windows {
// move to list
patch_data.patches_union.emplace_back(union_patch_data);
}
if (setting_union_patches_enabled.contains(patch_data.hash)) {
patch_data.selected_union_name = setting_union_patches_enabled[patch_data.hash];
}
@@ -2742,7 +2756,7 @@ namespace overlay::windows {
union_patch.data_len);
}
}
if (union_patch.data_offset_ptr == nullptr) {
return false;
}
+2 -2
View File
@@ -10,8 +10,8 @@ namespace overlay::windows {
~ScreenResize() override;
void build_content() override;
void update();
void update() override;
private:
size_t toggle_screen_resize = ~0u;
bool toggle_screen_resize_state = false;
+7
View File
@@ -4,6 +4,7 @@
#include <thread>
#include <mutex>
#include <vector>
#include <map>
#include <windows.h>
@@ -120,9 +121,15 @@ namespace rawinput {
std::vector<std::string> value_caps_names;
std::vector<HIDP_VALUE_CAPS> value_output_caps_list;
std::vector<std::string> value_output_caps_names;
std::vector<std::vector<bool>> button_states;
std::vector<std::vector<double>> button_up, button_down;
std::vector<std::vector<bool>> button_output_states;
// key: usage page, link collection
// value: number of buttons for that key (combine ranges and nonranges)
std::map<std::pair<USAGE, ULONG>, ULONG> button_usage_pages;
std::vector<float> value_states;
std::vector<LONG> value_states_raw;
std::vector<float> value_output_states;
+64 -33
View File
@@ -405,6 +405,7 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
std::vector<std::string> button_caps_names;
std::vector<std::vector<bool>> button_states;
std::vector<std::vector<double>> button_up, button_down;
std::map<std::pair<USAGE, ULONG>, ULONG> button_usage_pages;
for (int button_cap_num = 0; button_cap_num < button_cap_length; button_cap_num++) {
auto &button_caps = button_cap_data[button_cap_num];
@@ -436,6 +437,7 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
button_states.emplace_back(std::vector<bool>(static_cast<unsigned int>(button_count), false));
button_up.emplace_back(std::vector<double>(static_cast<unsigned int>(button_count), 0.0));
button_down.emplace_back(std::vector<double>(static_cast<unsigned int>(button_count), 0.0));
button_usage_pages[std::make_pair(button_caps.UsagePage, button_caps.LinkCollection)] += button_count;
// names
for (USAGE usg = button_caps.Range.UsageMin; usg <= button_caps.Range.UsageMax; usg++) {
@@ -757,6 +759,7 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
new_device.hidInfo->button_up = std::move(button_up);
new_device.hidInfo->button_down = std::move(button_down);
new_device.hidInfo->button_output_states = std::move(button_output_states);
new_device.hidInfo->button_usage_pages = std::move(button_usage_pages);
new_device.hidInfo->value_states = std::move(value_states);
new_device.hidInfo->value_states_raw = std::move(value_states_raw);
new_device.hidInfo->value_output_states = std::move(value_output_states);
@@ -1713,53 +1716,81 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
// get HID data
auto &data_hid = data->data.hid;
// buttons
for (size_t cap_num = 0; cap_num < device.hidInfo->button_caps_list.size(); cap_num++) {
auto &button_caps = device.hidInfo->button_caps_list[cap_num];
auto &button_states = device.hidInfo->button_states[cap_num];
auto &button_down = device.hidInfo->button_down[cap_num];
auto &button_up = device.hidInfo->button_up[cap_num];
// parse reports
for (const auto &pair : device.hidInfo->button_usage_pages) {
const auto usage_page = pair.first.first;
const auto link_collection = pair.first.second;
const auto button_count = pair.second;
// get button count
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
if (button_count <= 0) {
continue;
}
// get usages
auto usages_length = static_cast<ULONG>(button_count);
ULONG usages_length = button_count;
std::vector<USAGE> usages(static_cast<size_t>(usages_length));
if (HidP_GetUsages(
HidP_Input,
button_caps.UsagePage,
button_caps.LinkCollection,
usage_page,
link_collection,
usages.data(),
&usages_length,
reinterpret_cast<PHIDP_PREPARSED_DATA>(device.hidInfo->preparsed_data.get()),
reinterpret_cast<PCHAR>(data_hid.bRawData),
data_hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
// log_warning(
// "rawinput",
// "failed to get usages for device {}, usage page {:x} and link collection {:x}",
// device.desc,
// usage_page, link_collection);
continue;
}
// update buttons
std::vector<bool> new_states(button_count);
for (ULONG usage_num = 0; usage_num < usages_length; usage_num++) {
USAGE usage = usages[usage_num] - button_caps.Range.UsageMin;
// log_info(
// "rawinput",
// "processing HID input for device {}, usage page {:x} and link collection {:x} with {} buttons, got {} reports",
// device.desc,
// usage_page, link_collection, button_count, usages_length);
// guard against some buggy device sending an event for a usage below `UsageMin`
if (usage < button_count) {
new_states[usage] = true;
// buttons
for (size_t cap_num = 0; cap_num < device.hidInfo->button_caps_list.size(); cap_num++) {
auto &button_caps = device.hidInfo->button_caps_list[cap_num];
auto &button_states = device.hidInfo->button_states[cap_num];
auto &button_down = device.hidInfo->button_down[cap_num];
auto &button_up = device.hidInfo->button_up[cap_num];
// is this the right usage page and link collection?
if (button_caps.UsagePage != usage_page || button_caps.LinkCollection != link_collection) {
continue;
}
}
for (int button_num = 0; button_num < button_count; button_num++) {
if (!new_states[button_num] && button_states[button_num]) {
device.updated = true;
button_states[button_num] = new_states[button_num];
button_down[button_num] = input_time;
} else if (new_states[button_num] && !button_states[button_num]) {
device.updated = true;
button_states[button_num] = new_states[button_num];
button_up[button_num] = input_time;
// get button count
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
if (button_count <= 0) {
continue;
}
// update buttons
std::vector<bool> new_states(button_count);
for (ULONG usage_num = 0; usage_num < usages_length; usage_num++) {
if (usages[usage_num] < button_caps.Range.UsageMin ||
usages[usage_num] > button_caps.Range.UsageMax) {
continue;
}
USAGE usage = usages[usage_num] - button_caps.Range.UsageMin;
// guard against some buggy device sending an event for a usage below `UsageMin`
if (usage < button_count) {
new_states[usage] = true;
}
}
for (int button_num = 0; button_num < button_count; button_num++) {
if (!new_states[button_num] && button_states[button_num]) {
device.updated = true;
button_states[button_num] = new_states[button_num];
button_down[button_num] = input_time;
} else if (new_states[button_num] && !button_states[button_num]) {
device.updated = true;
button_states[button_num] = new_states[button_num];
button_up[button_num] = input_time;
}
}
}
}
+20
View File
@@ -0,0 +1,20 @@
1 VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "CompanyName", "SpiceTools"
VALUE "FileDescription", "SpiceTools DLL Stub"
VALUE "FileVersion", "1.0.0.0"
VALUE "ProductName", "SpiceTools DLL Stub"
VALUE "ProductVersion", "1.0.0.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0409, 1252
END
END
+7 -4
View File
@@ -1,3 +1,6 @@
// GetCurrentProcessorNumberEx
#define _WIN32_WINNT 0x0601
#include "cpuutils.h"
#include <thread>
@@ -98,7 +101,7 @@ namespace cpuutils {
log_misc("cpuutils", "detected {} processors", PROCESSOR_COUNT);
done = true;
// init processor states
get_load();
}
@@ -144,11 +147,11 @@ namespace cpuutils {
// dump cpu id
log_misc("cpuinfo", "{}, {} ({})",
cpu.vendor,
cpu.brand_string,
cpu.brand_string,
GetX86MicroarchitectureName(GetX86Microarchitecture(&cpu)));
log_misc("cpuinfo", "family 0x{:x}, model 0x{:x}, stepping 0x{:x}", cpu.family, cpu.model, cpu.stepping);
// dump features
// dump features
std::string features = "";
for (size_t i = 0; i < X86_LAST_; ++i) {
if (GetX86FeaturesEnumValue(&cpu.features, static_cast<X86FeaturesEnum>(i))) {
@@ -253,7 +256,7 @@ namespace cpuutils {
if (GetLogicalProcessorInformationEx == nullptr) {
return;
}
// determine buffer size
returned_length = 0;
result = GetLogicalProcessorInformationEx(
+106 -1
View File
@@ -9,6 +9,16 @@
#include "peb.h"
#include "util/fileutils.h"
#include "util/dependencies.h"
#include "util/unique_plain_ptr.h"
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
std::filesystem::path libutils::module_file_name(HMODULE module) {
std::wstring buf;
@@ -361,11 +371,106 @@ void libutils::check_duplicate_dlls() {
void libutils::warn_if_dll_exists(const std::string &file_name) {
if (fileutils::file_exists(MODULE_PATH / file_name)) {
log_info("libutils", "found user-supplied {} in modules directory", file_name);
libutils::print_dll_info(MODULE_PATH / file_name);
return;
}
const auto &spice_bin_path = libutils::module_file_name(nullptr).parent_path();
if (fileutils::file_exists(spice_bin_path / file_name)) {
log_info("libutils", "found user-supplied {} next to spice executable path", file_name);
libutils::print_dll_info(spice_bin_path / file_name);
return;
}
}
}
void libutils::print_dll_info(std::filesystem::path filename) {
DWORD handle;
const auto size = GetFileVersionInfoSizeA(filename.string().c_str(), &handle);
if (size == 0) {
log_debug(
"libutils",
"GetFileVersionInfoSizeA failed for {}: {}",
filename.filename().string(),
get_last_error_string());
return;
}
auto data = util::make_unique_plain<VOID>(size);
if (!GetFileVersionInfoA(filename.string().c_str(), handle, size, data.get())) {
log_debug(
"libutils",
"GetFileVersionInfoA failed for {}: {}",
filename.filename().string(),
get_last_error_string());
return;
}
struct LANGANDCODEPAGE {
WORD wLanguage;
WORD wCodePage;
} *lpTranslate = nullptr;
UINT cbTranslate = 0;
if (!VerQueryValueA(data.get(), "\\VarFileInfo\\Translation", (LPVOID*)&lpTranslate, &cbTranslate)) {
log_debug(
"libutils",
"VerQueryValueA failed for {}: {}",
filename.filename().string(),
get_last_error_string());
return;
}
if (cbTranslate == 0 || lpTranslate == nullptr) {
log_debug(
"libutils",
"VerQueryValueA returned invalid results for {}",
filename.filename().string());
return;
}
// pick language - prefer English
WORD lang = 0, codepage = 0;
for (UINT i = 0; i < cbTranslate / sizeof(*lpTranslate); i++) {
if (lpTranslate[i].wLanguage == 0x0409) { // en-us
lang = lpTranslate[i].wLanguage;
codepage = lpTranslate[i].wCodePage;
break;
}
}
if (lang == 0) {
lang = lpTranslate[0].wLanguage;
codepage = lpTranslate[0].wCodePage;
}
// StringFileInfo helper
auto query_string = [&](const char* key) -> std::string {
std::string subBlock = fmt::format(
"\\StringFileInfo\\{:04x}{:04x}\\{}",
lang, codepage, key
);
char* value = nullptr;
UINT size_out = 0;
if (VerQueryValueA(data.get(), subBlock.c_str(), (LPVOID*)&value, &size_out) && value) {
return value;
}
log_debug(
"libutils",
"VerQueryValueA({}) failed for {}: {}",
subBlock,
filename.filename().string(),
get_last_error_string());
return "";
};
const auto company_name = query_string("CompanyName");
const auto product_name = query_string("ProductName");
const auto version_str = query_string("FileVersion");
log_info(
"libutils",
"DLL info for {}: CompanyName = {}, ProductName = {}, Version = {}",
filename.filename().string(),
company_name.empty() ? "?" : company_name,
product_name.empty() ? "?" : product_name,
version_str.empty() ? "?" : version_str);
}
+2
View File
@@ -77,4 +77,6 @@ namespace libutils {
intptr_t rva2offset(const std::filesystem::path &path, intptr_t rva);
intptr_t offset2rva(IMAGE_NT_HEADERS *nt_headers, intptr_t offset);
intptr_t offset2rva(const std::filesystem::path &path, intptr_t offset);
void print_dll_info(std::filesystem::path filename);
}
+6 -6
View File
@@ -62,9 +62,9 @@ void show_popup_for_crash() {
text += "Game has crashed.\n\n";
text += "Check log.txt and look for error messages near the end of file.\n\n";
text += "Unsure what to do next?\n";
text += " * update spice2x to the latest version\n";
text += " * check the FAQ on spice2x github wiki\n";
text += " * do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n";
text += " * Update spice2x to the latest version\n";
text += " * Check the FAQ on spice2x github wiki\n";
text += " * Do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n";
text += "Press Enter, Esc, Alt+F4, or click OK to exit. Otherwise, game will close in 30 seconds.";
show_popup(text);
}
@@ -76,9 +76,9 @@ void show_popup_for_fatal_error(std::string message) {
text += message;
text += "----------------------------------------------------------\n\n";
text += "Unsure what to do next?\n";
text += " * update spice2x to the latest version\n";
text += " * check the FAQ on spice2x github wiki\n";
text += " * do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n";
text += " * Update spice2x to the latest version\n";
text += " * Check the FAQ on spice2x github wiki\n";
text += " * Do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n";
text += "Press Enter, Esc, Alt+F4, or click OK to exit. Otherwise, game will close in 30 seconds.";
show_popup(text);
}
+2 -2
View File
@@ -56,8 +56,8 @@ public:
template<class T, class... Args>
auto add(T&& func, Args&&... args)
-> std::future<typename std::result_of<T(Args...)>::type> {
using ret_t = typename std::result_of<T(Args...)>::type;
-> std::future<typename std::invoke_result<T, Args...>::type> {
using ret_t = typename std::invoke_result<T, Args...>::type;
auto task = std::make_shared<std::packaged_task<ret_t()>>(
std::bind(std::forward<T>(func), std::forward<Args>(args)...));
std::future<ret_t> fut = task->get_future();