Compare commits

...

36 Commits

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

For XP builds, the call is compiled out to avoid linking against dwmapi
and windows_dll_compat_checker returns successfully when testing against
the XP Embedded image. I can't test it on XP, but it ran on Vista.
2026-04-15 18:51:53 -07:00
bicarus a8419bbaa0 grpahics: fix borderless for ddr/gitadora (#638)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Allow DDR to be launched in borderless window (honor the spicecfg
option).

Prevent Gitadora arena model from changing the window decoration once
launched as it hangs the game.

## Testing
Tested 32/64bit ddr, gitadora arena.
2026-04-14 18:11:42 -07:00
bicarus 0fc430ceb3 popn: add io emulation for more lights and coin (#637)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Wire up coin and more lights to bi3a_hook.

## Testing
Tested on U region
2026-04-14 09:46:13 -07:00
bicarus e59a50b64c popn: boot with 3 monitors (#636)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Take the monitor hooks added for IIDX/SDVX #612 and apply it to popn as
well now that popn has a subscreen.

There is a debug option in game that actually uses three monitors, which
can be activated via hex edits. This will prevent that from working.

## Testing
Tested with 3 monitors, fs and windowed.
2026-04-13 23:02:56 -07:00
bicarus 2042b629e9 popn: allow game to launch on PCs with only one monitor (#633)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
The game insists on having two monitors. Hook various DX9 and Win32
monitor APIs to fake a monitor if the user only has one. This correctly
convinces the game that it needs to draw two screens instead of just
one.

## Testing
WIP
2026-04-13 03:00:47 -07:00
bicarus 440b9ed749 overlay: fix how input is handled when window loses focus (#632)
## Link to GitHub Issue or related Pull Request, if one exists
#286 

## Description of change
When we migrated to event-based I/O for ImGui, it introduced an issue
where a button would be held down even when the window is not in focus,
because we didn't notify ImGui that the button was released (after the
window lost focus).

Fix the ImGui custom I/O handler so that it immediately releases all
buttons when the window focus is lost.

## Testing
tested on 32-bit windowed mode
2026-04-12 04:44:46 -07:00
bicarus 9936088286 popn: subscreen overlay (#631)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change

Add overlay for subscreen.

Add an option for no subscreen window.

Use the same "force redraw subscreen" logic we used in SDVX to fix the
same issue.

Known issues:

* still crashing when running windowed if there is only one monitor
* having only one monitor causes sunscreen overlay to not work
(NumberOfAdaptersInGroup issue?)

## Testing
2026-04-12 02:11:24 -07:00
bicarus 4d8a6c1952 graphics: fix window position if out of bounds (#630)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Before calling `CreateWindowEx[A/W]`, check dimensions of the window
about to be created, and check if it will be out of bounds (completely
or partially).

If it's completely out of bounds, move it to `(0, 0)` to prevent crashes
or rendering issues.

## Testing
Tested popn and sdvx.
2026-04-11 20:12:43 -07:00
bicarus af69cf9251 popn: new cab lights (#629)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Basic support for lights - subwoofer RGB and button lights.

## Testing
Tested on title screen and in-game.

Tested beat pop as well since that lights up the colors in white/blue.
2026-04-11 18:08:45 -07:00
bicarus 1d5550b1e8 popn: implement bi3a-hook (bio3) (#626)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Turns out running this with bi2x doesn't process buttons properly, so we
have to use bi3a.

## Testing
Seems playable now.
2026-04-11 14:43:45 -07:00
bicarus ec3f6639b7 ccj: fix locale issues with carding in (#628)
## Link to GitHub Issue or related Pull Request, if one exists
#624 

## Description of change
See bug. Add locale hooks specifically for CCJ.

also, fix y axis direction for XInput, it was flipped because XInput
uses opposite values from what RawInput does

## Testing
Tested card in / play one round with Windows regional format set to
French (France) and other language set to English.
2026-04-11 14:42:51 -07:00
bicarus 727b99effd rawinput: fix ID for rawinput devices (#627)
## Link to GitHub Issue or related Pull Request, if one exists
#616 

## Description of change
The IDs don't really get used anywhere if it's not a real HID or MIDI
device, but putting them for completeness since they show up in Controls
overlay menu.

## Testing
2026-04-11 12:04:12 -07:00
bicarus 8afff3aaca popn: implement bi2x hook (#623)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Implement BI2X hook I/O for service/test/coin/headphones and the deka
pop-kuns.
2026-04-10 17:46:12 -07:00
bicarus b281517429 popn: hook DisplayConfigGetDeviceInfo to allow for any GPU port to be used for monitor, get past I/O check (#622)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
The game expects the main display to be *not* HDMI, and at port 2.

Hooking DisplayConfigGetDeviceInfo allows us to boot the game on any
monitor.

Add basic I/O hook. It gets stuck in `USB I/O Checking...` for a couple
minutes but it'll eventually skip and boot to title screen.

## Testing
ugh.
2026-04-10 02:56:32 -07:00
bicarus 54ec9f272e popn: new cab windowed mode (#621)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Get windowed mode to work.

## Testing
tbd... i/o doesn't work right now but it launches two windows at
least...
2026-04-10 01:05:44 -07:00
bicarus a159691097 xinput: bind UI, vibration, hotplug (#620)
## Link to GitHub Issue or related Pull Request, if one exists
#616 

## Description of change
`Naive` button is now called `Naive/XInput` and detects XInput
controllers

Add vibration output as "Lights"

Add proper hotplug support and multiple controllers

Fix misc bugs on x86

## Testing
I tested with two xbox controllers.
2026-04-10 01:04:20 -07:00
bicarus ca70f7b19c popn: distinguish between hpm and new popn (#619)
## Link to GitHub Issue or related Pull Request, if one exists
#618

## Description of change
Enhance game detection logic.

Fix heap size.
2026-04-09 19:54:27 -07:00
bicarus 7b4227d2a9 rawinput: add basic support for xinput (#617)
## Link to GitHub Issue or related Pull Request, if one exists
#616 

## Description of change
Adds preliminary support for XInput controllers. Enough to manually bind
buttons and map analogs.
2026-04-09 02:49:20 -07:00
bicarus f89efa2558 build: flatten file structure in release archives (#615)
Before:

```
.\spice2x\spice.exe
.\spice2x\spicecfg.exe
.\spice2x\spice64.exe
.\spice2x\stubs\..
.\spice2x\extras\..
```

after:

```
.\spice.exe
.\spicecfg.exe
.\spice64.exe
.\stubs\..
.\extras\..
```
2026-04-07 16:48:31 -07:00
bicarus 6f4824b648 misc: improve monitor logging (#614) 2026-04-06 22:22:34 -07:00
bicarus 45debeb5c9 iidx, sdvx: booting fullscreen with three or more monitors (#612)
## Link to GitHub Issue or related Pull Request, if one exists
#345 

Hoping that #180 is also addressed by this change, but I can't test it.

## Description of change

IIDX (TDJ) and SDVX (Valk) will now be able to boot in full screen even
with 3 or more monitors attached.

spice will "hide" from the game any monitors other than the primary
monitor and one other monitor for subscreen, allowing it to boot without
crashing - accomplished by clearing `DISPLAY_DEVICE_ATTACHED_TO_DESKTOP`
flag when monitors are enumerated over `EnumDisplayDevicesA`, and
fortunately both games seem to respect that flag.

Add options (one for IIDX, another for SDVX) that lets you specify the
monitor ID of the second monitor.

When the option is set, user-specified monitor will be used as the
subscreen. When the option is not set, spice will just pick the
lowest-indexed monitor as the subscreen, not counting the primary one of
course.

## Testing
Tested both games with three monitors. Should probably work for four or
more.
2026-04-06 02:59:12 -07:00
bicarus-dev a01f452eba fix build script 2026-04-06 02:37:40 -07:00
Will e452734bc1 Build a WinXP compatible version of Spice (#611)
This was a lot more background work than expected, but that is the
nature of unnecessary hobbies I guess. Most things in here are obvious,
so here's the non-obvious thoughts and notes:

1. All the XP stuff is optional, if the toolchain isn't available it
won't even try
2. The docker build is the "known good" build and now has all the
required tools for XP and checking binaries
a. If you're OK with it, can add the XP_MUST_BUILD flag to
build_docker.sh
4. New `SPICE_XP` flag to comment out blocks
5. We could technically build using the Docker image I've made for my XP
compatible LLVM toolchain, but combining the binaries into a single zip
at the end of it is somewhat tedious, so I've chosen to grab the
binaries from my release
6. I also wrote a DLL checker to make sure the final binaries are
_actually_ XP compatible, which found some issues on x86_64 (I don't
know a single game that runs on 64 bit XP, but it wasn't too much effort
so whatever). This also won't run if missing, just to save the trouble.
7. The build script is still pretty shit, I've made some minor
improvements
  a. `/usr/bin/env bash` instead of `/bin/bash` as it's more correct
  b. Adding `-it` to the docker build, so Ctrl+C actually halts it
c. Main issue is that failures don't halt, because of the custom error
catching function. This has caused a lot of frustration, maybe we can
change that

## Testing
It runs on 32 and 64 bit installs of XP. My jubeat cab is on Win10 these
days and I haven't got around to testing an actual game, but I've
distributed test builds of this to people who *have* confirmed it works
as expected.
2026-04-06 01:14:29 -07:00
bicarus d1779b93fa overlay: remove tab stop from invisible selectable used in options tab (#613)
#598 added extra tab stops to the invisible Selectable widgets, which is
only used for detecting mouse hover
2026-04-06 01:02:56 -07:00
bicarus fe9aac7e29 overlay: fix cosmetic issue in options tab when there are no options for the game (#610)
Number of columns was modified in #598 but it didn't correctly account
for the case where a game doesn't have any game-specific options.
2026-04-05 19:01:09 -07:00
bicarus-dev f3d887ebcf update strings 2026-04-05 17:03:23 -07:00
67 changed files with 3229 additions and 486 deletions
+4
View File
@@ -383,6 +383,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/shared/twtouch.cpp games/shared/twtouch.cpp
games/popn/popn.cpp games/popn/popn.cpp
games/popn/io.cpp games/popn/io.cpp
games/popn/bi3a_hook.cpp
games/bbc/bbc.cpp games/bbc/bbc.cpp
games/bbc/io.cpp games/bbc/io.cpp
games/hpm/hpm.cpp games/hpm/hpm.cpp
@@ -582,6 +583,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
overlay/windows/log.cpp overlay/windows/log.cpp
overlay/windows/midi.cpp overlay/windows/midi.cpp
overlay/windows/patch_manager.cpp overlay/windows/patch_manager.cpp
overlay/windows/popn_sub.cpp
overlay/windows/wnd_manager.cpp overlay/windows/wnd_manager.cpp
# rawinput # rawinput
@@ -596,6 +598,8 @@ set(SOURCE_FILES ${SOURCE_FILES}
rawinput/smxstage.h rawinput/smxstage.h
rawinput/smxdedicab.cpp rawinput/smxdedicab.cpp
rawinput/smxdedicab.h rawinput/smxdedicab.h
rawinput/xinput.cpp
rawinput/xinput.h
# reader # reader
reader/reader.cpp reader/reader.cpp
+122 -20
View File
@@ -35,24 +35,33 @@ struct ICCA_STATUS_LA9 {
static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes"); static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes");
enum ICCA_WORKFLOW { enum ICCA_WORKFLOW {
STEP, STEP = 0,
SLEEP, SLEEP = 1,
START, START = 2,
INIT, INIT = 3,
READY, READY = 4,
GET_USERID, GET_USERID = 5,
ACTIVE, ACTIVE = 6,
EJECT, EJECT = 7,
EJECT_CHECK, EJECT_CHECK = 8,
END, END = 9,
CLOSE_EJECT, CLOSE_EJECT = 10,
CLOSE_E_CHK, CLOSE_E_CHK = 11,
CLOSE_END, CLOSE_END = 12,
ERR_GETUID = -2 ERR_GETUID = -2,
ERR_REMOVED = -11
}; };
// used by RA as the status_code
enum ICCA_STATUS_CODE {
EMPTY = 1,
ENGAGED = 2
};
struct ICCA_UNIT { struct ICCA_UNIT {
struct ICCA_STATUS status {}; struct ICCA_STATUS status {};
enum ICCA_WORKFLOW state = STEP; enum ICCA_WORKFLOW state = STEP;
enum ICCA_WORKFLOW state_jdz = SLEEP;
bool card_cmd_pressed = false; bool card_cmd_pressed = false;
bool card_in = false; bool card_in = false;
double card_in_time = 0.0; double card_in_time = 0.0;
@@ -75,6 +84,11 @@ static inline int icca_get_active_count() {
} }
static inline int icca_get_unit_id(int unit_id) { static inline int icca_get_unit_id(int unit_id) {
// on RA the unit_id is zero-indexed
if (avs::game::is_model("JDZ")) {
return unit_id;
}
if (icca_get_active_count() < 2) if (icca_get_active_count() < 2)
return 1; return 1;
else { else {
@@ -122,9 +136,15 @@ static inline void update_card(int unit_id) {
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid); IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE; unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
unit->state_jdz = ACTIVE;
unit->status.error = 0; unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
} else { } else {
unit->state = ERR_GETUID; unit->state = ERR_GETUID;
unit->state_jdz = ERR_GETUID;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
memset(unit->status.uid, 0, 8); memset(unit->status.uid, 0, 8);
} }
unit->card_in = true; unit->card_in = true;
@@ -132,10 +152,25 @@ static inline void update_card(int unit_id) {
} else if (unit->state == EJECT_CHECK) { } else if (unit->state == EJECT_CHECK) {
unit->state = SLEEP; unit->state = SLEEP;
unit->card_in = false; unit->card_in = false;
unit->status.front_sensor = 0;
unit->status.rear_sensor = 0;
} else if (unit->state_jdz == READY) {
unit->state = GET_USERID;
unit->state_jdz = GET_USERID;
unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
unit->card_in = true;
unit->card_in_time = get_performance_seconds();
} }
} else { } else {
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE; unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
if (unit->state_jdz == READY) {
unit->state_jdz = GET_USERID;
}
unit->status.error = 0; unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
unit->card_in = true; unit->card_in = true;
unit->card_in_time = get_performance_seconds(); unit->card_in_time = get_performance_seconds();
} }
@@ -144,6 +179,12 @@ static inline void update_card(int unit_id) {
unit->state = CLOSE_EJECT; unit->state = CLOSE_EJECT;
if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) { if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) {
unit->card_in = false; unit->card_in = false;
memset(unit->status.uid, 0, 8);
if (unit->state_jdz == ACTIVE || unit->state_jdz == GET_USERID) {
unit->state_jdz = ERR_REMOVED;
}
unit->status.front_sensor = 0;
unit->status.rear_sensor = 0;
} }
} }
@@ -302,7 +343,11 @@ static char __cdecl ac_io_icca_get_status(void *a1, void *a2) {
// copy state to output buffer // copy state to output buffer
ICCA_UNIT *unit = &ICCA_UNITS[unit_id]; ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
unit->status.status_code = unit->state; if (avs::game::is_model("JDZ")) {
unit->status.status_code = unit->card_in ? ENGAGED : EMPTY;
} else {
unit->status.status_code = unit->state;
}
memcpy(status, &unit->status, sizeof(struct ICCA_STATUS)); memcpy(status, &unit->status, sizeof(struct ICCA_STATUS));
// funny workaround // funny workaround
@@ -387,6 +432,11 @@ static char __cdecl ac_io_icca_req_uid(int unit_id) {
static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) { static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) {
unit_id = icca_get_unit_id(unit_id); unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id]; ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
if (avs::game::is_model("JDZ")) {
// hack for RA - only ever seen it as this
*read_state = 8;
return 1;
}
if (unit->card_in) { if (unit->card_in) {
if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) { if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) {
unit->state = END; unit->state = END;
@@ -403,16 +453,61 @@ static int __cdecl ac_io_icca_send_keep_alive_packet(int a1, int a2, int a3) {
return 0; return 0;
} }
static int __cdecl ac_io_icca_workflow_jdz(int workflow, int unit_id) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
switch (workflow) {
// RA uses "STEP" as more of a polling thing it seems
case STEP:
switch (unit->state_jdz) {
case CLOSE_EJECT:
unit->state_jdz = CLOSE_E_CHK;
break;
case CLOSE_E_CHK:
unit->state_jdz = CLOSE_END;
break;
case CLOSE_END:
unit->state_jdz = SLEEP;
break;
default:
break;
}
break;
case SLEEP:
unit->state = SLEEP;
break;
case START:
unit->state_jdz = INIT;
break;
case INIT:
case READY:
unit->state_jdz = READY;
break;
case CLOSE_EJECT:
unit->state_jdz = CLOSE_EJECT;
return CLOSE_E_CHK;
case CLOSE_END:
unit->state_jdz = SLEEP;
break;
default:
// should probably log a warning here
break;
}
return unit->state_jdz;
}
static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) { static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
// RA uses a different workflow process for the slotted readers; treat it differently
if (avs::game::is_model("JDZ")) {
return ac_io_icca_workflow_jdz(workflow, unit_id);
}
unit_id = icca_get_unit_id(unit_id); unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id]; ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
switch (workflow) { switch (workflow) {
case STEP: case STEP:
if (avs::game::is_model("JDZ")) unit->state = STEP;
unit->state = SLEEP;
else
unit->state = STEP;
break; break;
case SLEEP: case SLEEP:
unit->state = SLEEP; unit->state = SLEEP;
@@ -441,7 +536,6 @@ static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
default: default:
break; break;
} }
return unit->state; return unit->state;
} }
@@ -467,6 +561,14 @@ acio::ICCAModule::ICCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModu
void acio::ICCAModule::attach() { void acio::ICCAModule::attach() {
ACIOModule::attach(); ACIOModule::attach();
// workaround for RA; ac_io_icca_cardunit_init is never called so treat like both are initialized
if (avs::game::is_model("JDZ")) {
ICCA_UNITS[0].initialized = true;
ICCA_UNITS[1].initialized = true;
CARD_TIMEOUT = 10.0; // keep the card in the slot for longer
}
// hooks // hooks
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init); ACIO_MODULE_HOOK(ac_io_icca_cardunit_init);
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished); ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished);
+10
View File
@@ -7,6 +7,7 @@
#include <stdint.h> #include <stdint.h>
#include "external/robin_hood.h" #include "external/robin_hood.h"
#include "games/popn/popn.h"
#include "launcher/logger.h" #include "launcher/logger.h"
#include "launcher/signal.h" #include "launcher/signal.h"
#include "util/deferlog.h" #include "util/deferlog.h"
@@ -1179,6 +1180,15 @@ namespace avs {
return; return;
} }
// hack, both hello! popn and popn hc using popn.dll
if (games::popn::is_pikapika_model()) {
auto old_size = HEAP_SIZE;
HEAP_SIZE = 0x20000000;
DEFAULT_HEAP_SIZE_SET = true;
log_info("avs-core", "updated heap size: {} -> {}", old_size, avs::core::HEAP_SIZE);
return;
}
if (HEAP_SIZE_DEFAULTS.find(dll_name) != HEAP_SIZE_DEFAULTS.end()) { if (HEAP_SIZE_DEFAULTS.find(dll_name) != HEAP_SIZE_DEFAULTS.end()) {
auto old_size = HEAP_SIZE; auto old_size = HEAP_SIZE;
+90 -4
View File
@@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# set to EN-US for consistency # set to EN-US for consistency
LANG=en_us_8859_1 LANG=en_us_8859_1
@@ -43,10 +43,16 @@ GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
GIT_HEAD=$(git rev-parse HEAD || echo "none") GIT_HEAD=$(git rev-parse HEAD || echo "none")
TOOLCHAIN_32="${TOOLCHAIN_32:-"/usr/share/mingw/toolchain-i686-w64-mingw32.cmake"}" TOOLCHAIN_32="${TOOLCHAIN_32:-"/usr/share/mingw/toolchain-i686-w64-mingw32.cmake"}"
TOOLCHAIN_64="${TOOLCHAIN_64:-"/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake"}" TOOLCHAIN_64="${TOOLCHAIN_64:-"/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake"}"
TOOLCHAIN_WINXP_32="${TOOLCHAIN_WINXP_32:-"/opt/llvm-mingw-xp/toolchain-files/cmake/i686-mingw32-clang.cmake"}"
TOOLCHAIN_WINXP_64="${TOOLCHAIN_WINXP_64:-"/opt/llvm-mingw-xp/toolchain-files/cmake/x86_64-mingw32-clang.cmake"}"
BUILDDIR_32_RELEASE="./cmake-build-release-32" BUILDDIR_32_RELEASE="./cmake-build-release-32"
BUILDDIR_32_DEBUG="./cmake-build-debug-32" BUILDDIR_32_DEBUG="./cmake-build-debug-32"
BUILDDIR_64_RELEASE="./cmake-build-release-64" BUILDDIR_64_RELEASE="./cmake-build-release-64"
BUILDDIR_64_DEBUG="./cmake-build-debug-64" BUILDDIR_64_DEBUG="./cmake-build-debug-64"
BUILDDIR_WINXP_32_RELEASE="./cmake-build-release-winxp-32"
BUILDDIR_WINXP_32_DEBUG="./cmake-build-debug-winxp-32"
BUILDDIR_WINXP_64_RELEASE="./cmake-build-release-winxp-64"
BUILDDIR_WINXP_64_DEBUG="./cmake-build-debug-winxp-64"
DEBUG=0 DEBUG=0
OUTDIR="./bin/spice2x" OUTDIR="./bin/spice2x"
OUTDIR_EXTRAS="./bin/spice2x/extras" OUTDIR_EXTRAS="./bin/spice2x/extras"
@@ -64,19 +70,36 @@ DIST_NAME_EXTRAS="spice2x-$(date +%y)-$(date +%m)-$(date +%d)-full.zip"
DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.' DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.'
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm" TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm"
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux" TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux"
TARGETS_XP32="spicetools_cfg spicetools_spice"
TARGETS_XP64="spicetools_spice64"
# determine build type # determine build type
BUILD_TYPE="Release" BUILD_TYPE="Release"
BUILDDIR_32=${BUILDDIR_32_RELEASE} BUILDDIR_32=${BUILDDIR_32_RELEASE}
BUILDDIR_64=${BUILDDIR_64_RELEASE} BUILDDIR_64=${BUILDDIR_64_RELEASE}
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_RELEASE}
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_RELEASE}
if ((DEBUG > 0)) if ((DEBUG > 0))
then then
BUILD_TYPE="Debug" BUILD_TYPE="Debug"
BUILDDIR_32=${BUILDDIR_32_DEBUG} BUILDDIR_32=${BUILDDIR_32_DEBUG}
BUILDDIR_64=${BUILDDIR_64_DEBUG} BUILDDIR_64=${BUILDDIR_64_DEBUG}
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_DEBUG}
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_DEBUG}
DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/') DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
fi fi
# is the XP-compatible toolchain installed?
XP_MUST_BUILD=0
BUILD_XP=0
if [ -f "$TOOLCHAIN_WINXP_32" ] && [ -f "$TOOLCHAIN_WINXP_64" ]; then
BUILD_XP=1;
elif ((XP_MUST_BUILD > 0))
then
echo "WinXP toolchain not available, aborting"
exit 1
fi
# determine number of cores # determine number of cores
CORES=$(nproc) CORES=$(nproc)
@@ -90,6 +113,13 @@ echo "Git Branch: $GIT_BRANCH"
echo "Git Head: $GIT_HEAD" echo "Git Head: $GIT_HEAD"
echo "Toolchain for 32bit targets: $TOOLCHAIN_32" echo "Toolchain for 32bit targets: $TOOLCHAIN_32"
echo "Toolchain for 64bit targets: $TOOLCHAIN_64" echo "Toolchain for 64bit targets: $TOOLCHAIN_64"
if ((BUILD_XP > 0))
then
echo "Toolchain for WinXP 32bit targets: $TOOLCHAIN_WINXP_32"
echo "Toolchain for WinXP 64bit targets: $TOOLCHAIN_WINXP_64"
else
echo "WinXP toolchain not available, skipping WinXP builds"
fi
echo "Distribution Name: $DIST_NAME" echo "Distribution Name: $DIST_NAME"
echo "Build Type: $BUILD_TYPE" echo "Build Type: $BUILD_TYPE"
echo "Cores: $CORES" echo "Cores: $CORES"
@@ -130,11 +160,57 @@ time (
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64} cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64}
popd > /dev/null popd > /dev/null
if ((BUILD_XP > 0))
then
# 32 bit Windows XP
echo "Building 32bit targets (WinXP toolchain)..."
echo "========================="
if ((CLEAN_BUILD > 0))
then
rm -rf ${BUILDDIR_WINXP_32}
fi
mkdir -p ${BUILDDIR_WINXP_32}
pushd ${BUILDDIR_WINXP_32} > /dev/null
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP32}
popd > /dev/null
# 64 bit Windows XP
echo ""
echo "Building 64bit targets (WinXP toolchain)..."
echo "========================="
if ((CLEAN_BUILD > 0))
then
rm -rf ${BUILDDIR_WINXP_64}
fi
mkdir -p ${BUILDDIR_WINXP_64}
pushd ${BUILDDIR_WINXP_64} > /dev/null
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP64}
popd > /dev/null
else
echo "Skipping WinXP builds, toolchain not specified"
fi
echo "" echo ""
echo "Compilation process done :)" echo "Compilation process done :)"
echo "===========================" echo "==========================="
) )
if ((BUILD_XP > 0))
then
echo ""
echo "Checking XP compatibility..."
echo "============================="
if ! command -v windows_dll_compat_checker &> /dev/null; then
echo "WARNING: windows_dll_compat_checker not found, skipping XP compatibility check"
else
windows_dll_compat_checker -s PREMADE/winxp_x86_64.ini \
${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe
windows_dll_compat_checker -s PREMADE/winxp_x86_64_32bit_dlls.ini \
${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe \
${BUILDDIR_WINXP_32}/spicetools/32/spice.exe
fi
fi
# generate PDBs # generate PDBs
if false # ((DEBUG > 0)) if false # ((DEBUG > 0))
then then
@@ -191,6 +267,10 @@ rm -rf ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS} mkdir -p ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware
mkdir -p ${OUTDIR_EXTRAS}/linux mkdir -p ${OUTDIR_EXTRAS}/linux
if ((BUILD_XP > 0))
then
mkdir -p ${OUTDIR_EXTRAS}/winxp
fi
if false # ((DEBUG > 0)) if false # ((DEBUG > 0))
then then
@@ -226,6 +306,12 @@ else
cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
if ((BUILD_XP > 0))
then
cp ${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
cp ${BUILDDIR_WINXP_32}/spicetools/32/spice.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
cp ${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
fi
fi fi
# pack source files to output directory # pack source files to output directory
@@ -250,10 +336,10 @@ then
echo "Building dist..." echo "Building dist..."
mkdir -p ${DIST_FOLDER} mkdir -p ${DIST_FOLDER}
rm -rf "${DIST_FOLDER}/${DIST_NAME}" rm -rf "${DIST_FOLDER}/${DIST_NAME}"
pushd ${OUTDIR}/.. > /dev/null pushd ${OUTDIR} > /dev/null
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT" zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" . -x "extras/*" -z <<< "$DIST_COMMENT"
echo "Building extras..." echo "Building extras..."
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS}" spice2x -z <<< "$DIST_COMMENT" zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS}" . -z <<< "$DIST_COMMENT"
popd > /dev/null popd > /dev/null
fi fi
+10 -2
View File
@@ -1,4 +1,12 @@
#!/bin/bash #!/usr/bin/env bash
set -eu
docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64 docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest
docker run --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
# Interactive TTY if available, so docker build can be Ctrl+C'd
DOCKER_FLAGS=""
[ -t 0 ] && DOCKER_FLAGS="-it"
docker run $DOCKER_FLAGS --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
+4
View File
@@ -90,6 +90,10 @@ std::string Analog::getDisplayString(rawinput::RawInputManager *manager) {
return "MIDI Unknown Index " + indexString + " (" + device->desc + ")"; return "MIDI Unknown Index " + indexString + " (" + device->desc + ")";
} }
} }
case rawinput::XINPUT_GAMEPAD:
return fmt::format("{} ({})",
xinput::get_analog_string(static_cast<xinput::XInputAnalogEnum>(index)),
device->desc);
case rawinput::DESTROYED: case rawinput::DESTROYED:
return "Device unplugged (" + indexString + ")"; return "Device unplugged (" + indexString + ")";
default: default:
+40 -5
View File
@@ -1,5 +1,6 @@
#include "api.h" #include "api.h"
#include <cassert>
#include <optional> #include <optional>
#include "launcher/superexit.h" #include "launcher/superexit.h"
@@ -389,6 +390,16 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
} }
case rawinput::PIUIO_DEVICE: { case rawinput::PIUIO_DEVICE: {
state = device->piuioDev->IsPressed(vKey) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED; state = device->piuioDev->IsPressed(vKey) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
break;
}
case rawinput::XINPUT_GAMEPAD: {
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
state = manager->XINPUT_MGR->is_button_pressed(
player,
static_cast<xinput::XInputButtonEnum>(vKey)) ?
BUTTON_PRESSED : BUTTON_NOT_PRESSED;
break;
} }
default: default:
break; break;
@@ -586,7 +597,7 @@ float Buttons::getVelocity(std::unique_ptr<rawinput::RawInputManager> &manager,
} }
} }
float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) { float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog) {
float value = 0.5f; float value = 0.5f;
if (!device) { if (!device) {
return value; return value;
@@ -767,6 +778,18 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
} }
break; break;
} }
case rawinput::XINPUT_GAMEPAD: {
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
value = manager->XINPUT_MGR->get_analog_state(
player,
static_cast<xinput::XInputAnalogEnum>(index));
// invert value
if (inverted) {
value = 1.f - value;
}
break;
}
default: default:
break; break;
} }
@@ -822,7 +845,7 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &ana
return analog.getLastState(); return analog.getLastState();
} }
float state = getState(device, analog); float state = getState(manager, device, analog);
analog.setLastState(state); analog.setLastState(state);
return state; return state;
@@ -904,7 +927,7 @@ void GameAPI::Lights::sortLightsWithCategory(
} }
void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float value) { void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, rawinput::Device *device, int index, float value) {
// check device // check device
if (!device) { if (!device) {
@@ -991,6 +1014,18 @@ void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float valu
} }
break; break;
} }
case rawinput::XINPUT_GAMEPAD: {
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
if (index < static_cast<int>(xinput::XInputOutputEnum::COUNT)) {
manager->XINPUT_MGR->set_output_state(
player, static_cast<xinput::XInputOutputEnum>(index), value);
// no need to set output_pending; xinput output handled immediately
} else {
log_warning("api", "invalid xinput light index: {}", index);
}
break;
}
default: default:
break; break;
} }
@@ -1016,9 +1051,9 @@ void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, Light &ligh
// write state // write state
if (light.override_enabled) { if (light.override_enabled) {
writeLight(device, light.getIndex(), light.override_state); writeLight(manager, device, light.getIndex(), light.override_state);
} else { } else {
writeLight(device, light.getIndex(), value); writeLight(manager, device, light.getIndex(), value);
} }
} }
+2 -2
View File
@@ -103,7 +103,7 @@ namespace GameAPI {
} }
} }
float getState(rawinput::Device *device, Analog &analog); float getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog);
float getState(rawinput::RawInputManager *manager, Analog &analog); float getState(rawinput::RawInputManager *manager, Analog &analog);
float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog); float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog);
} }
@@ -135,7 +135,7 @@ namespace GameAPI {
void sortLightsWithCategory(std::vector<Light> *lights, const std::initializer_list<LightAndCategory> list); void sortLightsWithCategory(std::vector<Light> *lights, const std::initializer_list<LightAndCategory> list);
void writeLight(rawinput::Device *device, int index, float value); void writeLight(rawinput::RawInputManager *manager, rawinput::Device *device, int index, float value);
void writeLight(rawinput::RawInputManager *manager, Light &light, float value); void writeLight(rawinput::RawInputManager *manager, Light &light, float value);
void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value); void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value);
+4
View File
@@ -417,6 +417,10 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
} }
case rawinput::PIUIO_DEVICE: case rawinput::PIUIO_DEVICE:
return "PIUIO " + vKeyString; return "PIUIO " + vKeyString;
case rawinput::XINPUT_GAMEPAD:
return fmt::format("{} ({})",
xinput::get_button_string(static_cast<xinput::XInputButtonEnum>(vKey)),
device->desc);
case rawinput::DESTROYED: case rawinput::DESTROYED:
return "Device unplugged (" + vKeyString + ")"; return "Device unplugged (" + vKeyString + ")";
default: default:
+9
View File
@@ -85,6 +85,15 @@ std::string Light::getDisplayString(rawinput::RawInputManager* manager) {
return "Invalid SMX Dedicab Light (" + index_string + ")"; return "Invalid SMX Dedicab Light (" + index_string + ")";
} }
case rawinput::XINPUT_GAMEPAD: {
if (index < static_cast<size_t>(xinput::XInputOutputEnum::COUNT)) {
return fmt::format("{} ({})",
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(index)),
device->desc);
}
return "Invalid XInput Gamepad Light (" + index_string + ")";
}
case rawinput::DESTROYED: case rawinput::DESTROYED:
return "Unplugged device (" + index_string + ")"; return "Unplugged device (" + index_string + ")";
default: default:
+1
View File
@@ -60,6 +60,7 @@ namespace cfg {
// window = rectangle including the frame // window = rectangle including the frame
// client = just the content area without frames. // client = just the content area without frames.
bool window_always_on_top = false; bool window_always_on_top = false;
bool window_disable_round_corners = false;
bool client_keep_aspect_ratio = true; bool client_keep_aspect_ratio = true;
bool enable_window_resize = false; bool enable_window_resize = false;
int window_decoration = 0; // enum type WindowDecorationMode int window_decoration = 0; // enum type WindowDecorationMode
+8
View File
@@ -15,3 +15,11 @@ RUN pacman --noconfirm -Syu git \
ccache ccache
RUN useradd user -m && echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers RUN useradd user -m && echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
RUN su user -c "cd /tmp/ && git clone https://aur.archlinux.org/yay-bin.git --depth=1 && cd yay-bin && makepkg --noconfirm -si && yay --noconfirm -S mingw-w64-cmake" RUN su user -c "cd /tmp/ && git clone https://aur.archlinux.org/yay-bin.git --depth=1 && cd yay-bin && makepkg --noconfirm -si && yay --noconfirm -S mingw-w64-cmake"
RUN mkdir -p /opt/llvm-mingw-xp \
&& curl -fsSL "https://github.com/mon/llvm-mingw-xp/releases/download/llvm-mingw-xp-22.1.0/llvm-mingw-llvm-mingw-xp-22.1.0-msvcrt-ubuntu-22.04-x86_64.tar.xz" \
| tar -xJ --strip-components=1 -C /opt/llvm-mingw-xp
ENV PATH="$PATH:/opt/llvm-mingw-xp/bin"
RUN curl -fsSL "https://github.com/mon/windows-dll-compat-checker/releases/download/v1.3/windows_dll_compat_checker-linux-x86_64.tar.xz" \
| tar -xJ -C /usr/local/bin
+1
View File
@@ -174,6 +174,7 @@ namespace games::gitadora {
static_assert(sizeof(AIO_IOB5_Y32D) == 0x2e8); static_assert(sizeof(AIO_IOB5_Y32D) == 0x2e8);
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4570); static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4570);
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48); static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
static_assert(sizeof(AIO_IOB2_BI2X_AC1__DEVSTATUS) == 0x2C8);
/* /*
* typedefs * typedefs
+7 -7
View File
@@ -1,6 +1,6 @@
#include "camera.h" #include "camera.h"
#if SPICE64 #if SPICE64 && !SPICE_XP
#include <d3d9.h> #include <d3d9.h>
#include "mf_wrappers.h" #include "mf_wrappers.h"
@@ -84,7 +84,7 @@ namespace games::iidx {
bool find_camera_hooks() { bool find_camera_hooks() {
std::string dll_name = avs::game::DLL_NAME; std::string dll_name = avs::game::DLL_NAME;
dll_path = MODULE_PATH / dll_name; dll_path = MODULE_PATH / dll_name;
iidx_module = libutils::try_module(dll_path); iidx_module = libutils::try_module(dll_path);
if (!iidx_module) { if (!iidx_module) {
@@ -149,7 +149,7 @@ namespace games::iidx {
"E800000000488BC8488BD34883C420", "E800000000488BC8488BD34883C420",
"X????XXXXXXXXXX", "X????XXXXXXXXXX",
0, 0)); 0, 0));
if (addr_textures_ptr == nullptr) { if (addr_textures_ptr == nullptr) {
log_warning("iidx:camhook", "failed to find hook: addr_textures (part 1)"); log_warning("iidx:camhook", "failed to find hook: addr_textures (part 1)");
return FALSE; return FALSE;
@@ -232,7 +232,7 @@ namespace games::iidx {
} }
static void **__fastcall camera_hook_a(PBYTE a1) { static void **__fastcall camera_hook_a(PBYTE a1) {
std::call_once(hook_a_init, [&]{ std::call_once(hook_a_init, [&]{
device = *reinterpret_cast<LPDIRECT3DDEVICE9EX*>(a1 + addr_device_offset); device = *reinterpret_cast<LPDIRECT3DDEVICE9EX*>(a1 + addr_device_offset);
auto const preview = *reinterpret_cast<LPDIRECT3DTEXTURE9**>((uint8_t*)iidx_module + addr_textures); auto const preview = *reinterpret_cast<LPDIRECT3DTEXTURE9**>((uint8_t*)iidx_module + addr_textures);
auto const manager = reinterpret_cast<Camera::CCameraManager2*>((uint8_t*)iidx_module + addr_camera_manager); auto const manager = reinterpret_cast<Camera::CCameraManager2*>((uint8_t*)iidx_module + addr_camera_manager);
@@ -298,11 +298,11 @@ namespace games::iidx {
// Ask for source type = video capture devices. // Ask for source type = video capture devices.
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
hr = pAttributes->SetGUID( hr = pAttributes->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
); );
} }
// Enumerate devices. // Enumerate devices.
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
hr = WrappedMFEnumDeviceSources(pAttributes, &ppDevices, &numDevices); hr = WrappedMFEnumDeviceSources(pAttributes, &ppDevices, &numDevices);
@@ -478,7 +478,7 @@ namespace games::iidx {
init_mf_library(); init_mf_library();
} }
} }
return result; return result;
} }
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#if SPICE64 #if SPICE64 && !SPICE_XP
#include <vector> #include <vector>
#include "games/iidx/local_camera.h" #include "games/iidx/local_camera.h"
+18 -10
View File
@@ -30,6 +30,7 @@
#include "util/memutils.h" #include "util/memutils.h"
#include "util/sigscan.h" #include "util/sigscan.h"
#include "util/socd_cleaner.h" #include "util/socd_cleaner.h"
#include "util/sysutils.h"
#include "util/time.h" #include "util/time.h"
#include "util/utils.h" #include "util/utils.h"
#include "launcher/signal.h" #include "launcher/signal.h"
@@ -423,6 +424,14 @@ namespace games::iidx {
init_legacy_camera_hook(FLIP_CAMS); init_legacy_camera_hook(FLIP_CAMS);
} }
#if SPICE64
if (TDJ_MODE) {
sysutils::hook_EnumDisplayDevicesA();
}
#endif
// init cfgmgr32 hooks // init cfgmgr32 hooks
cfgmgr32hook_init(avs::game::DLL_INSTANCE); cfgmgr32hook_init(avs::game::DLL_INSTANCE);
} }
@@ -486,28 +495,28 @@ namespace games::iidx {
log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead"); log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead");
} }
// check -monitor + TDJ mode // check -dxmainadapter + TDJ mode
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && TDJ_MODE) { if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && TDJ_MODE) {
IIDX_TDJ_MONITOR_WARNING = true; IIDX_TDJ_MONITOR_WARNING = true;
log_warning( log_warning(
"iidx", "iidx",
"\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n" "\n\n"
"!!! compatibility issues with the game !!!\n" "!!! using -dxmainadapter option with TDJ is NOT recommended !!!\n"
"!!! due to known compatibility issues with the game !!!\n"
"!!! !!!\n" "!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n" "!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n" "!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n" "!!! !!!\n"
"!!! recommendation is to NOT use -monitor and instead set the !!!\n" "!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
"!!! primary monitor in Windows settings before launching the game !!!\n\n" "!!! option instead of -dxmainadapter !!!\n\n"
); );
deferredlogs::defer_error_messages({ deferredlogs::defer_error_messages({
"-monitor option is NOT recommended when running with TDJ mode", "-dxmainadapter option is NOT recommended when running with TDJ mode",
" due to known compatibility issues with the game:", " due to known compatibility issues with the game:",
" * game may launch in wrong resolution or refresh rate", " * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay", " * touch / mouse input may stop working in subscreen / overlay",
" recommended fix is to NOT use -monitor and instead set the primary", " use Change Main Monitor (-mainmonitor) option instead"
" monitor in Windows settings before launching the game"
}); });
} }
@@ -906,8 +915,7 @@ namespace games::iidx {
log_warning( log_warning(
"iidx", "iidx",
"Failed to apply ASIO compatibility fix for iidx32+. " "Failed to apply ASIO compatibility fix for iidx32+. "
"Unless patches are applied, your ASIO device ({}) may hang or fail to work", "Unless patches are applied, your ASIO device may hang or fail to work");
ASIO_DRIVER->c_str());
} else { } else {
log_info( log_info(
"iidx", "iidx",
+31 -31
View File
@@ -1,6 +1,6 @@
#include "games/iidx/local_camera.h" #include "games/iidx/local_camera.h"
#if SPICE64 #if SPICE64 && !SPICE_XP
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h" #include "util/utils.h"
@@ -128,7 +128,7 @@ namespace games::iidx {
// TODO: Color space conversion // TODO: Color space conversion
// if (SUCCEEDED(hr)) { // if (SUCCEEDED(hr)) {
// hr = pAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE); // hr = pAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE);
// } // }
// if (SUCCEEDED(hr)) { // if (SUCCEEDED(hr)) {
// hr = pAttributes->SetUINT32(MF_READWRITE_DISABLE_CONVERTERS, FALSE); // hr = pAttributes->SetUINT32(MF_READWRITE_DISABLE_CONVERTERS, FALSE);
// } // }
@@ -189,11 +189,11 @@ namespace games::iidx {
&pType &pType
); );
if (FAILED(hr)) { if (FAILED(hr)) {
if (hr != MF_E_NO_MORE_TYPES) { if (hr != MF_E_NO_MORE_TYPES) {
log_warning("iidx:camhook", "[{}] Cannot get media type {} {:#x}", m_name, i, (ULONG)hr); log_warning("iidx:camhook", "[{}] Cannot get media type {} {:#x}", m_name, i, (ULONG)hr);
} }
break; break;
} }
hr = TryMediaType(pType, &bestWidth, &bestFrameRate); hr = TryMediaType(pType, &bestWidth, &bestFrameRate);
@@ -246,7 +246,7 @@ namespace games::iidx {
pSelectedMediaType = m_pAutoMediaType; pSelectedMediaType = m_pAutoMediaType;
} }
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
hr = ChangeMediaType(pSelectedMediaType); hr = ChangeMediaType(pSelectedMediaType);
} }
@@ -281,7 +281,7 @@ namespace games::iidx {
m_cameraHeight = info.height; m_cameraHeight = info.height;
UpdateDrawRect(); UpdateDrawRect();
} }
return hr; return hr;
} }
@@ -395,7 +395,7 @@ namespace games::iidx {
m_device->ColorFill(m_pDestSurf, &targetRect, D3DCOLOR_XRGB(0, 0, 0)); m_device->ColorFill(m_pDestSurf, &targetRect, D3DCOLOR_XRGB(0, 0, 0));
} }
void IIDXLocalCamera::CreateThread() { void IIDXLocalCamera::CreateThread() {
// Create thread // Create thread
m_drawThread = new std::thread([this]() { m_drawThread = new std::thread([this]() {
@@ -411,7 +411,7 @@ namespace games::iidx {
if (accumulator > 1.0) { if (accumulator > 1.0) {
accumulator -= 1.0; accumulator -= 1.0;
floorFrameTimeMicroSec += 1; floorFrameTimeMicroSec += 1;
} }
std::this_thread::sleep_for(std::chrono::microseconds(floorFrameTimeMicroSec)); std::this_thread::sleep_for(std::chrono::microseconds(floorFrameTimeMicroSec));
} }
}); });
@@ -430,12 +430,12 @@ namespace games::iidx {
log_misc("iidx:camhook", "[{}] Init camera control", m_name); log_misc("iidx:camhook", "[{}] Init camera control", m_name);
hr = m_pSource->QueryInterface(IID_IAMCameraControl, (void**)&m_pCameraControl); hr = m_pSource->QueryInterface(IID_IAMCameraControl, (void**)&m_pCameraControl);
if (FAILED(hr)) { if (FAILED(hr)) {
// The device does not support IAMCameraControl // The device does not support IAMCameraControl
log_warning("iidx:camhook", "[{}] Camera control not supported", m_name); log_warning("iidx:camhook", "[{}] Camera control not supported", m_name);
return E_FAIL; return E_FAIL;
} }
for (size_t i = 0; i < CAMERA_CONTROL_PROP_SIZE; i++) { for (size_t i = 0; i < CAMERA_CONTROL_PROP_SIZE; i++) {
long minValue = 0; long minValue = 0;
@@ -472,9 +472,9 @@ namespace games::iidx {
CameraControlProp prop = m_controlProps.at(i); CameraControlProp prop = m_controlProps.at(i);
log_misc( log_misc(
"iidx:camhook", "[{}] >> {} range=({}, {}) default={} delta={} dFlags={} value={} vFlags={}", "iidx:camhook", "[{}] >> {} range=({}, {}) default={} delta={} dFlags={} value={} vFlags={}",
m_name, m_name,
CAMERA_CONTROL_LABELS[i], CAMERA_CONTROL_LABELS[i],
prop.minValue, prop.maxValue, prop.minValue, prop.maxValue,
prop.defaultValue, prop.defaultValue,
prop.delta, prop.delta,
@@ -570,9 +570,9 @@ namespace games::iidx {
// Get frame rate // Get frame rate
hr = MFGetAttributeRatio( hr = MFGetAttributeRatio(
pType, pType,
MF_MT_FRAME_RATE, MF_MT_FRAME_RATE,
(UINT32*)&frameRate.Numerator, (UINT32*)&frameRate.Numerator,
(UINT32*)&frameRate.Denominator (UINT32*)&frameRate.Denominator
); );
if (FAILED(hr)) { goto done; } if (FAILED(hr)) { goto done; }
@@ -580,7 +580,7 @@ namespace games::iidx {
info.frameRate = frameRate.Numerator / frameRate.Denominator; info.frameRate = frameRate.Numerator / frameRate.Denominator;
info.description = fmt::format( info.description = fmt::format(
"{}x{} @{}FPS {}", "{}x{} @{}FPS {}",
info.width, info.width,
info.height, info.height,
(int)info.frameRate, (int)info.frameRate,
@@ -606,7 +606,7 @@ namespace games::iidx {
return "Unknown"; return "Unknown";
} }
/** /**
* Return values: * Return values:
* S_OK: this is a "better" media type than the existing one * S_OK: this is a "better" media type than the existing one
* S_FALSE: valid media type, but not "better" * S_FALSE: valid media type, but not "better"
@@ -620,17 +620,17 @@ namespace games::iidx {
hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype); hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
if (FAILED(hr)) { if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get subtype: {:#x}", m_name, (ULONG)hr); log_warning("iidx:camhook", "[{}] Failed to get subtype: {:#x}", m_name, (ULONG)hr);
return hr; return hr;
} }
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height); hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);
if (FAILED(hr)) { if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get frame size: {:#x}", m_name, (ULONG)hr); log_warning("iidx:camhook", "[{}] Failed to get frame size: {:#x}", m_name, (ULONG)hr);
return hr; return hr;
} }
// Only support format with converters // Only support format with converters
// TODO: verify conversion support with DXVA // TODO: verify conversion support with DXVA
if (subtype != MFVideoFormat_YUY2 && subtype != MFVideoFormat_NV12) { if (subtype != MFVideoFormat_YUY2 && subtype != MFVideoFormat_NV12) {
@@ -639,12 +639,12 @@ namespace games::iidx {
// Frame rate // Frame rate
hr = MFGetAttributeRatio( hr = MFGetAttributeRatio(
pType, pType,
MF_MT_FRAME_RATE, MF_MT_FRAME_RATE,
(UINT32*)&frameRate.Numerator, (UINT32*)&frameRate.Numerator,
(UINT32*)&frameRate.Denominator (UINT32*)&frameRate.Denominator
); );
if (FAILED(hr)) { if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get frame rate: {:#x}", m_name, (ULONG)hr); log_warning("iidx:camhook", "[{}] Failed to get frame rate: {:#x}", m_name, (ULONG)hr);
return hr; return hr;
} }
@@ -667,7 +667,7 @@ namespace games::iidx {
// Check if this format has better resolution / frame rate // Check if this format has better resolution / frame rate
if ((width > *pBestWidth) || (width >= (UINT32)TARGET_SURFACE_WIDTH && frameRateValue >= *pBestFrameRate)) { if ((width > *pBestWidth) || (width >= (UINT32)TARGET_SURFACE_WIDTH && frameRateValue >= *pBestFrameRate)) {
// log_misc( // log_misc(
// "iidx:camhook", "Better media type {} ({}x{}) @({} FPS)", // "iidx:camhook", "Better media type {} ({}x{}) @({} FPS)",
// GetVideoFormatName(subtype), // GetVideoFormatName(subtype),
// width, // width,
// height, // height,
@@ -730,7 +730,7 @@ namespace games::iidx {
HRESULT IIDXLocalCamera::FlushDrawCommands() { HRESULT IIDXLocalCamera::FlushDrawCommands() {
IDirect3DQuery9* pEventQuery = nullptr; IDirect3DQuery9* pEventQuery = nullptr;
// It is necessary to flush the command queue // It is necessary to flush the command queue
// or the data is not ready for the receiver to read. // or the data is not ready for the receiver to read.
// Adapted from : https://msdn.microsoft.com/en-us/library/windows/desktop/bb172234%28v=vs.85%29.aspx // Adapted from : https://msdn.microsoft.com/en-us/library/windows/desktop/bb172234%28v=vs.85%29.aspx
// Also see : http://www.ogre3d.org/forums/viewtopic.php?f=5&t=50486 // Also see : http://www.ogre3d.org/forums/viewtopic.php?f=5&t=50486
@@ -784,8 +784,8 @@ namespace games::iidx {
for (int y = 0; y < TARGET_SURFACE_HEIGHT; y++) { for (int y = 0; y < TARGET_SURFACE_HEIGHT; y++) {
for (int x = 0; x < TARGET_SURFACE_WIDTH; x++) { for (int x = 0; x < TARGET_SURFACE_WIDTH; x++) {
memcpy( memcpy(
pDest + x * pixelSize, pDest + x * pixelSize,
pSrc + (flip_h ? (TARGET_SURFACE_WIDTH - x - 1) : x) * pixelSize, pSrc + (flip_h ? (TARGET_SURFACE_WIDTH - x - 1) : x) * pixelSize,
pixelSize pixelSize
); );
} }
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#if SPICE64 #if SPICE64 && !SPICE_XP
#include <d3d9.h> #include <d3d9.h>
#include <dxva2api.h> #include <dxva2api.h>
+47 -46
View File
@@ -51,7 +51,7 @@ namespace games {
static robin_hood::unordered_map<std::string, std::vector<Analog> &> analogs; static robin_hood::unordered_map<std::string, std::vector<Analog> &> analogs;
static robin_hood::unordered_map<std::string, std::vector<Light> &> lights; static robin_hood::unordered_map<std::string, std::vector<Light> &> lights;
static robin_hood::unordered_map<std::string, std::vector<Option>> options; static robin_hood::unordered_map<std::string, std::vector<Option>> options;
static robin_hood::unordered_map<std::string, std::vector<std::string>> file_hints; static robin_hood::unordered_map<std::string, std::vector<std::vector<std::string>>> file_hints;
static void initialize() { static void initialize() {
@@ -68,7 +68,7 @@ namespace games {
buttons_help.insert({ bbc, bbc::get_buttons_help() }); buttons_help.insert({ bbc, bbc::get_buttons_help() });
analogs.insert({ bbc, bbc::get_analogs() }); analogs.insert({ bbc, bbc::get_analogs() });
lights.insert({ bbc, bbc::get_lights() }); lights.insert({ bbc, bbc::get_lights() });
file_hints[bbc].emplace_back("bsch.dll"); file_hints[bbc].push_back({"bsch.dll"});
// hpm // hpm
const std::string hpm("HELLO! Pop'n Music"); const std::string hpm("HELLO! Pop'n Music");
@@ -76,32 +76,32 @@ namespace games {
buttons.insert({ hpm, hpm::get_buttons() }); buttons.insert({ hpm, hpm::get_buttons() });
buttons_help.insert({ hpm, hpm::get_buttons_help() }); buttons_help.insert({ hpm, hpm::get_buttons_help() });
lights.insert({ hpm, hpm::get_lights() }); lights.insert({ hpm, hpm::get_lights() });
file_hints[hpm].emplace_back("popn.dll"); file_hints[hpm].push_back({"popn.dll", "pdh.dll"});
// bs // bs
const std::string bs("Beatstream"); const std::string bs("Beatstream");
games.push_back(bs); games.push_back(bs);
buttons.insert({ bs, bs::get_buttons() }); buttons.insert({ bs, bs::get_buttons() });
lights.insert({ bs, bs::get_lights() }); lights.insert({ bs, bs::get_lights() });
file_hints[bs].emplace_back("beatstream.dll"); file_hints[bs].push_back({"beatstream.dll"});
file_hints[bs].emplace_back("beatstream1.dll"); file_hints[bs].push_back({"beatstream1.dll"});
file_hints[bs].emplace_back("beatstream2.dll"); file_hints[bs].push_back({"beatstream2.dll"});
// ddr // ddr
const std::string ddr("Dance Dance Revolution"); const std::string ddr("Dance Dance Revolution");
games.push_back(ddr); games.push_back(ddr);
buttons.insert({ ddr, ddr::get_buttons() }); buttons.insert({ ddr, ddr::get_buttons() });
lights.insert({ ddr, ddr::get_lights() }); lights.insert({ ddr, ddr::get_lights() });
file_hints[ddr].emplace_back("ddr.dll"); file_hints[ddr].push_back({"ddr.dll"});
file_hints[ddr].emplace_back("mdxja_945.dll"); file_hints[ddr].push_back({"mdxja_945.dll"});
file_hints[ddr].emplace_back("arkmdxp3.dll"); file_hints[ddr].push_back({"arkmdxp3.dll"});
// dea // dea
const std::string dea("Dance Evolution"); const std::string dea("Dance Evolution");
games.push_back(dea); games.push_back(dea);
buttons.insert({ dea, dea::get_buttons() }); buttons.insert({ dea, dea::get_buttons() });
lights.insert({ dea, dea::get_lights() }); lights.insert({ dea, dea::get_lights() });
file_hints[dea].emplace_back("arkkdm.dll"); file_hints[dea].push_back({"arkkdm.dll"});
// gitadora // gitadora
const std::string gitadora("GitaDora"); const std::string gitadora("GitaDora");
@@ -111,7 +111,7 @@ namespace games {
analogs_help.insert({ gitadora, gitadora::get_analogs_help() }); analogs_help.insert({ gitadora, gitadora::get_analogs_help() });
analogs.insert({ gitadora, gitadora::get_analogs() }); analogs.insert({ gitadora, gitadora::get_analogs() });
lights.insert({ gitadora, gitadora::get_lights() }); lights.insert({ gitadora, gitadora::get_lights() });
file_hints[gitadora].emplace_back("gdxg.dll"); file_hints[gitadora].push_back({"gdxg.dll"});
// iidx // iidx
const std::string iidx("Beatmania IIDX"); const std::string iidx("Beatmania IIDX");
@@ -120,7 +120,7 @@ namespace games {
buttons_help.insert({ iidx, iidx::get_buttons_help() }); buttons_help.insert({ iidx, iidx::get_buttons_help() });
analogs.insert({ iidx, iidx::get_analogs() }); analogs.insert({ iidx, iidx::get_analogs() });
lights.insert({ iidx, iidx::get_lights() }); lights.insert({ iidx, iidx::get_lights() });
file_hints[iidx].emplace_back("bm2dx.dll"); file_hints[iidx].push_back({"bm2dx.dll"});
// jb // jb
const std::string jb("Jubeat"); const std::string jb("Jubeat");
@@ -128,7 +128,7 @@ namespace games {
buttons.insert({ jb, jb::get_buttons() }); buttons.insert({ jb, jb::get_buttons() });
buttons_help.insert({ jb, jb::get_buttons_help() }); buttons_help.insert({ jb, jb::get_buttons_help() });
lights.insert({ jb, jb::get_lights() }); lights.insert({ jb, jb::get_lights() });
file_hints[jb].emplace_back("jubeat.dll"); file_hints[jb].push_back({"jubeat.dll"});
// mga // mga
const std::string mga("Metal Gear"); const std::string mga("Metal Gear");
@@ -136,7 +136,7 @@ namespace games {
buttons.insert({ mga, mga::get_buttons() }); buttons.insert({ mga, mga::get_buttons() });
analogs.insert({ mga, mga::get_analogs() }); analogs.insert({ mga, mga::get_analogs() });
lights.insert({ mga, mga::get_lights() }); lights.insert({ mga, mga::get_lights() });
file_hints[mga].emplace_back("launch.dll"); file_hints[mga].push_back({"launch.dll"});
// museca // museca
const std::string museca("Museca"); const std::string museca("Museca");
@@ -145,7 +145,7 @@ namespace games {
buttons_help.insert({ museca, museca::get_buttons_help() }); buttons_help.insert({ museca, museca::get_buttons_help() });
analogs.insert({ museca, museca::get_analogs() }); analogs.insert({ museca, museca::get_analogs() });
lights.insert({ museca, museca::get_lights() }); lights.insert({ museca, museca::get_lights() });
file_hints[museca].emplace_back("museca.dll"); file_hints[museca].push_back({"museca.dll"});
// nost // nost
const std::string nost("Nostalgia"); const std::string nost("Nostalgia");
@@ -154,7 +154,7 @@ namespace games {
buttons_help.insert({ nost, nost::get_buttons_help() }); buttons_help.insert({ nost, nost::get_buttons_help() });
analogs.insert({ nost, nost::get_analogs() }); analogs.insert({ nost, nost::get_analogs() });
lights.insert({ nost, nost::get_lights() }); lights.insert({ nost, nost::get_lights() });
file_hints[nost].emplace_back("nostalgia.dll"); file_hints[nost].push_back({"nostalgia.dll"});
// popn // popn
const std::string popn("Pop'n Music"); const std::string popn("Pop'n Music");
@@ -162,41 +162,42 @@ namespace games {
buttons.insert({ popn, popn::get_buttons() }); buttons.insert({ popn, popn::get_buttons() });
buttons_help.insert({ popn, popn::get_buttons_help() }); buttons_help.insert({ popn, popn::get_buttons_help() });
lights.insert({ popn, popn::get_lights() }); lights.insert({ popn, popn::get_lights() });
file_hints[popn].emplace_back("popn19.dll"); file_hints[popn].push_back({"popn19.dll"});
file_hints[popn].emplace_back("popn20.dll"); file_hints[popn].push_back({"popn20.dll"});
file_hints[popn].emplace_back("popn21.dll"); file_hints[popn].push_back({"popn21.dll"});
file_hints[popn].emplace_back("popn22.dll"); file_hints[popn].push_back({"popn22.dll"});
file_hints[popn].emplace_back("popn23.dll"); file_hints[popn].push_back({"popn23.dll"});
file_hints[popn].emplace_back("popn24.dll"); file_hints[popn].push_back({"popn24.dll"});
file_hints[popn].emplace_back("popn25.dll"); file_hints[popn].push_back({"popn25.dll"});
file_hints[popn].push_back({"popn.dll", "libaio-iob2_video.dll"});
// qma // qma
const std::string qma("Quiz Magic Academy"); const std::string qma("Quiz Magic Academy");
games.push_back(qma); games.push_back(qma);
buttons.insert({ qma, qma::get_buttons() }); buttons.insert({ qma, qma::get_buttons() });
lights.insert({ qma, qma::get_lights() }); lights.insert({ qma, qma::get_lights() });
file_hints[qma].emplace_back("client.dll"); file_hints[qma].push_back({"client.dll"});
// rb // rb
const std::string rb("Reflec Beat"); const std::string rb("Reflec Beat");
games.push_back(rb); games.push_back(rb);
buttons.insert({ rb, rb::get_buttons() }); buttons.insert({ rb, rb::get_buttons() });
lights.insert({ rb, rb::get_lights() }); lights.insert({ rb, rb::get_lights() });
file_hints[rb].emplace_back("reflecbeat.dll"); file_hints[rb].push_back({"reflecbeat.dll"});
// shogikai // shogikai
std::string shogikai("Tenkaichi Shogikai"); std::string shogikai("Tenkaichi Shogikai");
games.push_back(shogikai); games.push_back(shogikai);
buttons.insert({ shogikai, shogikai::get_buttons() }); buttons.insert({ shogikai, shogikai::get_buttons() });
lights.insert({ shogikai, shogikai::get_lights() }); lights.insert({ shogikai, shogikai::get_lights() });
file_hints[shogikai].emplace_back("shogi_engine.dll"); file_hints[shogikai].push_back({"shogi_engine.dll"});
// rf3d // rf3d
const std::string rf3d("Road Fighters 3D"); const std::string rf3d("Road Fighters 3D");
games.push_back(rf3d); games.push_back(rf3d);
buttons.insert({ rf3d, rf3d::get_buttons() }); buttons.insert({ rf3d, rf3d::get_buttons() });
analogs.insert({ rf3d, rf3d::get_analogs() }); analogs.insert({ rf3d, rf3d::get_analogs() });
file_hints[rf3d].emplace_back("jgt.dll"); file_hints[rf3d].push_back({"jgt.dll"});
// sc // sc
const std::string sc("Steel Chronicle"); const std::string sc("Steel Chronicle");
@@ -204,7 +205,7 @@ namespace games {
buttons.insert({ sc, sc::get_buttons() }); buttons.insert({ sc, sc::get_buttons() });
analogs.insert({ sc, sc::get_analogs() }); analogs.insert({ sc, sc::get_analogs() });
lights.insert({ sc, sc::get_lights() }); lights.insert({ sc, sc::get_lights() });
file_hints[sc].emplace_back("gamekgg.dll"); file_hints[sc].push_back({"gamekgg.dll"});
// sdvx // sdvx
const std::string sdvx("Sound Voltex"); const std::string sdvx("Sound Voltex");
@@ -213,13 +214,13 @@ namespace games {
buttons_help.insert({ sdvx, sdvx::get_buttons_help() }); buttons_help.insert({ sdvx, sdvx::get_buttons_help() });
analogs.insert({ sdvx, sdvx::get_analogs() }); analogs.insert({ sdvx, sdvx::get_analogs() });
lights.insert({ sdvx, sdvx::get_lights() }); lights.insert({ sdvx, sdvx::get_lights() });
file_hints[sdvx].emplace_back("soundvoltex.dll"); file_hints[sdvx].push_back({"soundvoltex.dll"});
// mfc // mfc
const std::string mfc("Mahjong Fight Club"); const std::string mfc("Mahjong Fight Club");
games.push_back(mfc); games.push_back(mfc);
buttons.insert({ mfc, mfc::get_buttons() }); buttons.insert({ mfc, mfc::get_buttons() });
file_hints[mfc].emplace_back("allinone.dll"); file_hints[mfc].push_back({"allinone.dll"});
// ftt // ftt
const std::string ftt("FutureTomTom"); const std::string ftt("FutureTomTom");
@@ -228,21 +229,21 @@ namespace games {
buttons_help.insert({ ftt, ftt::get_buttons_help() }); buttons_help.insert({ ftt, ftt::get_buttons_help() });
analogs.insert({ ftt, ftt::get_analogs() }); analogs.insert({ ftt, ftt::get_analogs() });
lights.insert({ ftt, ftt::get_lights() }); lights.insert({ ftt, ftt::get_lights() });
file_hints[ftt].emplace_back("arkmmd.dll"); file_hints[ftt].push_back({"arkmmd.dll"});
// loveplus // loveplus
const std::string loveplus("LovePlus"); const std::string loveplus("LovePlus");
games.push_back(loveplus); games.push_back(loveplus);
buttons.insert({ loveplus, loveplus::get_buttons() }); buttons.insert({ loveplus, loveplus::get_buttons() });
lights.insert({ loveplus, loveplus::get_lights() }); lights.insert({ loveplus, loveplus::get_lights() });
file_hints[loveplus].emplace_back("arkklp.dll"); file_hints[loveplus].push_back({"arkklp.dll"});
// scotto // scotto
const std::string scotto("Scotto"); const std::string scotto("Scotto");
games.push_back(scotto); games.push_back(scotto);
buttons.insert({ scotto, scotto::get_buttons() }); buttons.insert({ scotto, scotto::get_buttons() });
lights.insert({ scotto, scotto::get_lights() }); lights.insert({ scotto, scotto::get_lights() });
file_hints[scotto].emplace_back("scotto.dll"); file_hints[scotto].push_back({"scotto.dll"});
// drs // drs
const std::string drs("DANCERUSH"); const std::string drs("DANCERUSH");
@@ -250,14 +251,14 @@ namespace games {
buttons.insert({ drs, drs::get_buttons() }); buttons.insert({ drs, drs::get_buttons() });
buttons_help.insert({ drs, drs::get_buttons_help() }); buttons_help.insert({ drs, drs::get_buttons_help() });
lights.insert({ drs, drs::get_lights() }); lights.insert({ drs, drs::get_lights() });
file_hints[drs].emplace_back("superstep.dll"); file_hints[drs].push_back({"superstep.dll"});
// otoca // otoca
const std::string otoca("Otoca D'or"); const std::string otoca("Otoca D'or");
games.push_back(otoca); games.push_back(otoca);
buttons.insert({ otoca, otoca::get_buttons() }); buttons.insert({ otoca, otoca::get_buttons() });
buttons_help.insert({ otoca, otoca::get_buttons_help() }); buttons_help.insert({ otoca, otoca::get_buttons_help() });
file_hints[otoca].emplace_back("arkkep.dll"); file_hints[otoca].push_back({"arkkep.dll"});
// winning eleven // winning eleven
const std::string we("Winning Eleven"); const std::string we("Winning Eleven");
@@ -265,27 +266,27 @@ namespace games {
buttons.insert({ we, we::get_buttons() }); buttons.insert({ we, we::get_buttons() });
analogs.insert({ we, we::get_analogs() }); analogs.insert({ we, we::get_analogs() });
lights.insert({ we, we::get_lights() }); lights.insert({ we, we::get_lights() });
file_hints[we].emplace_back("weac12_bootstrap_release.dll"); file_hints[we].push_back({"weac12_bootstrap_release.dll"});
file_hints[we].emplace_back("arknck.dll"); file_hints[we].push_back({"arknck.dll"});
// silent scope: bone eater // silent scope: bone eater
const std::string silentscope("Silent Scope: Bone Eater"); const std::string silentscope("Silent Scope: Bone Eater");
games.push_back(silentscope); games.push_back(silentscope);
buttons.insert({ silentscope, silentscope::get_buttons() }); buttons.insert({ silentscope, silentscope::get_buttons() });
analogs.insert({ silentscope, silentscope::get_analogs() }); analogs.insert({ silentscope, silentscope::get_analogs() });
file_hints[silentscope].emplace_back("arkndd.dll"); file_hints[silentscope].push_back({"arkndd.dll"});
// charge machine // charge machine
const std::string pcm("Charge Machine"); const std::string pcm("Charge Machine");
games.push_back(pcm); games.push_back(pcm);
buttons.insert({ pcm, pcm::get_buttons() }); buttons.insert({ pcm, pcm::get_buttons() });
file_hints[pcm].emplace_back("launch.dll"); file_hints[pcm].push_back({"launch.dll"});
// ongaku paradise // ongaku paradise
const std::string op("Ongaku Paradise"); const std::string op("Ongaku Paradise");
games.push_back(op); games.push_back(op);
buttons.insert({ op, onpara::get_buttons() }); buttons.insert({ op, onpara::get_buttons() });
file_hints[op].emplace_back("arkjc9.dll"); file_hints[op].push_back({"arkjc9.dll"});
// bc // bc
const std::string bc("Busou Shinki: Armored Princess Battle Conductor"); const std::string bc("Busou Shinki: Armored Princess Battle Conductor");
@@ -293,7 +294,7 @@ namespace games {
buttons.insert({ bc, bc::get_buttons() }); buttons.insert({ bc, bc::get_buttons() });
buttons_help.insert({ bc, bc::get_buttons_help() }); buttons_help.insert({ bc, bc::get_buttons_help() });
analogs.insert({ bc, bc::get_analogs() }); analogs.insert({ bc, bc::get_analogs() });
file_hints[bc].emplace_back("game/bsac_app.exe"); file_hints[bc].push_back({"game/bsac_app.exe"});
// ccj // ccj
const std::string ccj("Chase Chase Jokers"); const std::string ccj("Chase Chase Jokers");
@@ -302,21 +303,21 @@ namespace games {
buttons_help.insert({ ccj, ccj::get_buttons_help() }); buttons_help.insert({ ccj, ccj::get_buttons_help() });
analogs.insert({ ccj, ccj::get_analogs() }); analogs.insert({ ccj, ccj::get_analogs() });
lights.insert({ ccj, ccj::get_lights() }); lights.insert({ ccj, ccj::get_lights() });
file_hints[ccj].emplace_back("game/chaseproject.exe"); file_hints[ccj].push_back({"game/chaseproject.exe"});
// QuizKnock STADIUM // QuizKnock STADIUM
const std::string qks("QuizKnock STADIUM"); const std::string qks("QuizKnock STADIUM");
games.push_back(qks); games.push_back(qks);
buttons.insert({ qks, qks::get_buttons() }); buttons.insert({ qks, qks::get_buttons() });
buttons_help.insert({ qks, qks::get_buttons_help() }); buttons_help.insert({ qks, qks::get_buttons_help() });
file_hints[qks].emplace_back("game/uks.exe"); file_hints[qks].push_back({"game/uks.exe"});
// Mahjong Fight Girl // Mahjong Fight Girl
const std::string mfg("Mahjong Fight Girl"); const std::string mfg("Mahjong Fight Girl");
games.push_back(mfg); games.push_back(mfg);
buttons.insert({ mfg, mfg::get_buttons() }); buttons.insert({ mfg, mfg::get_buttons() });
buttons_help.insert({ mfg, mfg::get_buttons_help() }); buttons_help.insert({ mfg, mfg::get_buttons_help() });
file_hints[mfg].emplace_back("game/MFGClient_Data"); file_hints[mfg].push_back({"game/MFGClient_Data"});
// Polaris Chord // Polaris Chord
const std::string pc("Polaris Chord"); const std::string pc("Polaris Chord");
@@ -325,7 +326,7 @@ namespace games {
buttons_help.insert({ pc, pc::get_buttons_help() }); buttons_help.insert({ pc, pc::get_buttons_help() });
analogs.insert({ pc, pc::get_analogs() }); analogs.insert({ pc, pc::get_analogs() });
lights.insert({ pc, pc::get_lights() }); lights.insert({ pc, pc::get_lights() });
file_hints[pc].emplace_back("game/svm.exe"); file_hints[pc].push_back({"game/svm.exe"});
} }
const std::vector<std::string> &get_games() { const std::vector<std::string> &get_games() {
@@ -559,7 +560,7 @@ namespace games {
return &it->second; return &it->second;
} }
std::vector<std::string> *get_game_file_hints(const std::string &game) { std::vector<std::vector<std::string>> *get_game_file_hints(const std::string &game) {
initialize(); initialize();
auto it = file_hints.find(game); auto it = file_hints.find(game);
if (it == file_hints.end()) { if (it == file_hints.end()) {
+1 -1
View File
@@ -64,5 +64,5 @@ namespace games {
std::vector<Analog> *get_analogs(const std::string &game); std::vector<Analog> *get_analogs(const std::string &game);
std::vector<Light> *get_lights(const std::string &game); std::vector<Light> *get_lights(const std::string &game);
std::vector<Option> *get_options(const std::string &game); std::vector<Option> *get_options(const std::string &game);
std::vector<std::string> *get_game_file_hints(const std::string &game); std::vector<std::vector<std::string>> *get_game_file_hints(const std::string &game);
} }
+552
View File
@@ -0,0 +1,552 @@
#include "bi3a_hook.h"
#if SPICE64 && !SPICE_XP
#include <optional>
#include <cstdint>
#include "util/detour.h"
#include "util/logging.h"
#include "rawinput/rawinput.h"
#include "misc/eamuse.h"
#include "games/io.h"
#include "popn.h"
#include "io.h"
#include "util/tapeled.h"
#include <typeinfo>
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
namespace games::popn {
/*
* class definitions
*/
struct AIO_NMGR_IOB2 {
uint8_t data[0xe18];
};
struct AIO_NMGR_IOB5 {
uint8_t data[0xb10];
};
struct AIO_SCI_COMM {
uint8_t data[0x108];
};
struct AIO_SCI_COMM_T {
uint64_t unk_0;
struct AIO_SCI_COMM *aioSciComm;
};
struct AIO_COMM_STATUS {
uint64_t unk[5];
};
struct AIO_IOB5_BI3A {
uint8_t data[0x4FF0];
};
union AIO_IOB5_BI3A__DEVSTATUS {
uint8_t RawBytes[648];
struct {
uint8_t Reserved0[27]; // 0-26
uint8_t Coin0; // 27
uint8_t Coin1; // 28
uint8_t Reserved1[15]; // 29-43
uint8_t TestButton; // 44, io code offset rcx+0xe4
uint8_t ServiceButton; // 45, io code offset rcx+0xe5
uint8_t CoinMech; // 46, io code offset rcx+0xe6
uint8_t Reserved2[19]; // 47-65
uint8_t JOYL_SW; // 66
uint8_t Reserved3[4]; // 67-70
uint8_t JOYR_SW; // 71
uint8_t Headphones; // 72
uint8_t Recorder; // 73, unused
uint8_t Reserved4[295]; // 74-368
uint8_t Buttons_0_8; // 369
uint8_t Button_9; // 370
} Input;
};
// verified with M39-004-2025121500
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe18);
static_assert(sizeof(AIO_NMGR_IOB5) == 0xb10);
static_assert(sizeof(AIO_SCI_COMM) == 0x108);
static_assert(sizeof(AIO_IOB5_BI3A) == 0x4FF0);
static_assert(sizeof(AIO_IOB5_BI3A__DEVSTATUS) == 0x288);
/*
* typedefs
*/
// libaio.dll
typedef AIO_SCI_COMM_T *(__fastcall *aioSciComm_Open_t)(AIO_SCI_COMM_T *unk);
typedef void(__fastcall *aioSci_Destroy_t)(AIO_SCI_COMM *i_pNodeMgr);
typedef void(__fastcall *aioSci_GetCommStatus_t)(AIO_SCI_COMM *i_pNodeMgr, AIO_COMM_STATUS *i_Status);
typedef void(__fastcall *aioNodeMgr_Destroy_t)(AIO_NMGR_IOB5 *i_pNodeMgr);
typedef int32_t(__fastcall *aioNodeMgr_GetState_t)(AIO_NMGR_IOB5 *i_pNodeMgr);
typedef bool(__fastcall *aioNodeMgr_IsReady_t)(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State);
typedef bool(__fastcall *aioNodeMgr_IsError_t)(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State);
typedef void (__fastcall *aioNodeCtl_Destroy_t)(AIO_IOB5_BI3A *i_pNodeCtl);
typedef int32_t (__fastcall *aioNodeCtl_GetState_t)(AIO_IOB5_BI3A *i_pNodeCtl);
typedef bool (__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State);
typedef bool (__fastcall *aioNodeCtl_IsError_t)(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State);
typedef void(__fastcall *aioNodeCtl_UpdateDevicesStatus_t)();
// libaio-iob5.dll
typedef AIO_NMGR_IOB5 *(__fastcall *aioNMgrIob5_Create_t)(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode);
typedef AIO_IOB5_BI3A *(__fastcall *aioIob5Bi3a_Create_t)(AIO_NMGR_IOB5 *i_pNodeMgr, uint8_t i, void *p);
typedef void(__fastcall *aioIob5Bi3a_GetDeviceStatus_t)(AIO_IOB5_BI3A *i_pNodeCtl, AIO_IOB5_BI3A__DEVSTATUS *o_DevStatus);
typedef void(__fastcall *aioIob5Bi3a_SetOutputData_t)(AIO_IOB5_BI3A *a1, unsigned int a2, unsigned __int8 a3);
typedef void(__fastcall *aioIob5Bi3a_SetTapeLedDataPart_t)(AIO_IOB5_BI3A *a1, unsigned int a2, char a3, const void *a4, unsigned int a5, bool a6);
typedef int64_t (__fastcall *aioIob5Bi3a_SetTapeLedDataLimit_t)(AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_Channel, uint8_t i_Scale, uint8_t i_Limit);
/*
* function pointers
*/
// libaio.dll
static aioSciComm_Open_t aioSciComm_Open_orig = nullptr;
static aioSci_Destroy_t aioSci_Destroy_orig = nullptr;
static aioSci_GetCommStatus_t aioSci_GetCommStatus_orig = nullptr;
static aioNodeMgr_Destroy_t aioNodeMgr_Destroy_orig = nullptr;
static aioNodeMgr_GetState_t aioNodeMgr_GetState_orig = nullptr;
static aioNodeMgr_IsReady_t aioNodeMgr_IsReady_orig = nullptr;
static aioNodeMgr_IsError_t aioNodeMgr_IsError_orig = nullptr;
static aioNodeCtl_Destroy_t aioNodeCtl_Destroy_orig = nullptr;
static aioNodeCtl_GetState_t aioNodeCtl_GetState_orig = nullptr;
static aioNodeCtl_IsReady_t aioNodeCtl_IsReady_orig = nullptr;
static aioNodeCtl_IsError_t aioNodeCtl_IsError_orig = nullptr;
static aioNodeCtl_UpdateDevicesStatus_t aioNodeCtl_UpdateDevicesStatus_orig = nullptr;
// libaio-iob5.dll
static aioNMgrIob5_Create_t aioNMgrIob5_Create_orig = nullptr;
static aioIob5Bi3a_Create_t aioIob5Bi3a_Create_orig = nullptr;
static aioIob5Bi3a_GetDeviceStatus_t aioIob5Bi3a_GetDeviceStatus_orig = nullptr;
static aioIob5Bi3a_SetOutputData_t aioIob5Bi3a_SetOutputData_orig = nullptr;
static aioIob5Bi3a_SetTapeLedDataPart_t aioIob5Bi3a_SetTapeLedDataPart_orig = nullptr;
static aioIob5Bi3a_SetTapeLedDataLimit_t aioIob5Bi3a_SetTapeLedDataLimit_orig = nullptr;
/*
* variables
*/
static AIO_NMGR_IOB2 *aioNmgrIob2;
static AIO_NMGR_IOB5 *aioNmgrIob5;
static AIO_SCI_COMM *aioSciComm;
static AIO_IOB5_BI3A *aioIob5Bi3a;
/*
* implementations
*/
// libaio.dll
static AIO_SCI_COMM_T *__fastcall aioSciComm_Open(AIO_SCI_COMM_T *unk) {
aioSciComm = new AIO_SCI_COMM;
memset(unk, 0, sizeof(AIO_SCI_COMM_T));
unk->aioSciComm = aioSciComm;
return unk;
}
static void __fastcall aioSci_GetCommStatus(AIO_SCI_COMM *i_pNodeMgr, AIO_COMM_STATUS *i_Status) {
if (i_pNodeMgr == aioSciComm) {
memset(i_Status, 0, sizeof(AIO_COMM_STATUS));
} else {
return aioSci_GetCommStatus_orig(i_pNodeMgr, i_Status);
}
}
static void __fastcall aioSci_Destroy(AIO_SCI_COMM *i_pNodeMgr) {
if (i_pNodeMgr == aioSciComm) {
delete aioSciComm;
} else {
return aioSci_Destroy_orig(i_pNodeMgr);
}
}
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB5 *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
delete aioNmgrIob5;
aioNmgrIob5 = nullptr;
} else {
return aioNodeMgr_Destroy_orig(i_pNodeMgr);
}
}
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB5 *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
return 1;
} else {
return aioNodeMgr_GetState_orig(i_pNodeMgr);
}
}
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
return true;
} else {
return aioNodeMgr_IsReady_orig(i_pNodeMgr, i_State);
}
}
static bool __fastcall aioNodeMgr_IsError(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
return false;
} else {
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
}
}
static void __fastcall aioNodeCtl_Destroy(AIO_IOB5_BI3A *i_pNodeCtl) {
if (i_pNodeCtl != aioIob5Bi3a) {
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
}
delete aioIob5Bi3a;
aioIob5Bi3a = nullptr;
}
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB5_BI3A *i_pNodeCtl) {
if (i_pNodeCtl == aioIob5Bi3a) {
return 1;
}
return aioNodeCtl_GetState_orig(i_pNodeCtl);
}
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIob5Bi3a) {
return true;
}
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
}
static bool __fastcall aioNodeCtl_IsError(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIob5Bi3a) {
return false;
}
return aioNodeCtl_IsError_orig(i_pNodeCtl, i_State);
}
static void __fastcall aioNodeCtl_UpdateDevicesStatus() {
}
// libaio-iob5.dll
static AIO_NMGR_IOB5 *__fastcall aioNMgrIob5_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
if (i_pSci == aioSciComm) {
aioNmgrIob5 = new AIO_NMGR_IOB5;
log_info("bi3a_hook", "aioNMgrIob5_Create called, returning custom AIO_NMGR_IOB5: {}", fmt::ptr(aioNmgrIob5));
return aioNmgrIob5;
} else {
return aioNMgrIob5_Create_orig(i_pSci, i_bfMode);
}
}
static AIO_IOB5_BI3A *__fastcall aioIob5Bi3a_Create(AIO_NMGR_IOB5 *i_pNodeMgr, uint8_t i, void *p) {
log_info("bi3a_hook", "aioIob5Bi3a_Create called with i_pNodeMgr={}, i={}, p={}", fmt::ptr(i_pNodeMgr), i, fmt::ptr(p));
if (i_pNodeMgr == aioNmgrIob5) {
aioIob5Bi3a = new AIO_IOB5_BI3A;
log_info("bi3a_hook", "aioIob5Bi3a_Create: returning custom AIO_IOB5_BI3A: {}", fmt::ptr(aioIob5Bi3a));
return aioIob5Bi3a;
} else {
return aioIob5Bi3a_Create_orig(i_pNodeMgr, i, p);
}
}
static void __fastcall aioIob5Bi3a_GetDeviceStatus(AIO_IOB5_BI3A *i_pNodeCtl, AIO_IOB5_BI3A__DEVSTATUS *o_DevStatus) {
RI_MGR->devices_flush_output();
if (i_pNodeCtl != aioIob5Bi3a) {
return aioIob5Bi3a_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus);
}
memset(o_DevStatus, 0, sizeof(*o_DevStatus));
auto &buttons = get_buttons();
// operator
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test])) {
o_DevStatus->Input.TestButton = 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service])) {
o_DevStatus->Input.ServiceButton = 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech])) {
o_DevStatus->Input.CoinMech = 1;
}
// buttons 1-9
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button1])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 0);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button2])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 1);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button3])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 2);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button4])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 3);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button5])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 4);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button6])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 5);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button7])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 6);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button8])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 7);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button9])) {
o_DevStatus->Input.Button_9 = 1;
}
// deka pop-kuns
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::RedPop])) {
o_DevStatus->Input.JOYL_SW = 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BluePop])) {
o_DevStatus->Input.JOYR_SW = 1;
}
// audio
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone])) {
o_DevStatus->Input.Headphones = 1;
}
// coin
o_DevStatus->Input.Coin0 += eamuse_coin_get_stock();
}
static void __fastcall aioIob5Bi3a_SetOutputData(AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_CnPin, uint8_t i_Data) {
if (i_pNodeCtl != aioIob5Bi3a) {
return aioIob5Bi3a_SetOutputData_orig(i_pNodeCtl, i_CnPin, i_Data);
}
// 7 = IC_CARD_R
// 8 = IC_CARD_G
// 9 = IC_CARD_B
// 10 = WOOFER_R
// 11 = WOOFER_G
// 12 = WOOFER_B
// values = 0 to 255 in test menu
std::optional<Lights::popn_lights_t> light;
switch (i_CnPin) {
case 7:
light = Lights::IC_Card_R;
break;
case 8:
light = Lights::IC_Card_G;
break;
case 9:
light = Lights::IC_Card_B;
break;
case 10:
light = Lights::WooferLED_R;
break;
case 11:
light = Lights::WooferLED_G;
break;
case 12:
light = Lights::WooferLED_B;
break;
default:
break;
}
if (light.has_value()) {
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights.at(light.value()), i_Data / 255.f);
}
return;
}
static int64_t __fastcall aioIob5Bi3a_SetTapeLedDataLimit(
AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_Channel, uint8_t i_Scale, uint8_t i_Limit) {
if (i_pNodeCtl != aioIob5Bi3a) {
return aioIob5Bi3a_SetTapeLedDataLimit_orig(i_pNodeCtl, i_Channel, i_Scale, i_Limit);
}
return 0;
}
static void __fastcall aioIob5Bi3a_SetTapeLedDataPart(
AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_CnPin, char i_LedType, const void *i_pData, uint32_t i_DataSize, bool i_bIsLast) {
if (i_pNodeCtl != aioIob5Bi3a) {
return aioIob5Bi3a_SetTapeLedDataPart_orig(i_pNodeCtl, i_CnPin, i_LedType, i_pData, i_DataSize, i_bIsLast);
}
auto number_of_leds = i_DataSize;
// buttons 1-9, but game passes in wrong length; fix it up
if (i_CnPin == 0 && i_DataSize == 9) {
number_of_leds = i_DataSize * 3;
}
if (FALSE) {
std::string data;
for (uint32_t i = 0; i < number_of_leds; i++) {
auto dataByte = ((uint8_t *)i_pData)[i];
data += fmt::format("{:02X} ", dataByte);
}
log_info(
"bi3a_hook",
"aioIob5Bi3a_SetTapeLedDataPart Pin={}, Size={}, Type={}, data@{} = {}",
i_CnPin, number_of_leds, (uint8_t)i_LedType, fmt::ptr(i_pData), data);
}
auto &lights = get_lights();
if (i_CnPin == 0 && number_of_leds == 9 * 3) {
// special handling converting to non-RGB lights
// take the max(R, G, B) and use it to write the light value
uint8_t light_value[9] = { 0 };
for (uint32_t i = 0; i < number_of_leds; i += 3) {
light_value[i / 3] =
std::max({ ((uint8_t *)i_pData)[i], ((uint8_t *)i_pData)[i + 1], ((uint8_t *)i_pData)[i + 2] });
}
constexpr Lights::popn_lights_t legacy_button_lights[] = {
Lights::popn_lights_t::Button1,
Lights::popn_lights_t::Button2,
Lights::popn_lights_t::Button3,
Lights::popn_lights_t::Button4,
Lights::popn_lights_t::Button5,
Lights::popn_lights_t::Button6,
Lights::popn_lights_t::Button7,
Lights::popn_lights_t::Button8,
Lights::popn_lights_t::Button9
};
static_assert(std::size(legacy_button_lights) == 9);
for (size_t light = 0; light < 9; light++) {
// on the new cab, buttons are colorless plastic and they rely on RGB to be lit at all times, even when "off"
// when translating to legacy on/off lights, treat anything above ~60% brightness as fully on,
// otherwise off, to avoid dimly lit "off" state
GameAPI::Lights::writeLight(
RI_MGR,
lights.at(legacy_button_lights[light]),
light_value[light] > 150 ? 1.f : 0.f);
}
// color buttons
constexpr Lights::popn_lights_t button_lights[] = {
Lights::popn_lights_t::PikaButton1_R, Lights::popn_lights_t::PikaButton1_G, Lights::popn_lights_t::PikaButton1_B,
Lights::popn_lights_t::PikaButton2_R, Lights::popn_lights_t::PikaButton2_G, Lights::popn_lights_t::PikaButton2_B,
Lights::popn_lights_t::PikaButton3_R, Lights::popn_lights_t::PikaButton3_G, Lights::popn_lights_t::PikaButton3_B,
Lights::popn_lights_t::PikaButton4_R, Lights::popn_lights_t::PikaButton4_G, Lights::popn_lights_t::PikaButton4_B,
Lights::popn_lights_t::PikaButton5_R, Lights::popn_lights_t::PikaButton5_G, Lights::popn_lights_t::PikaButton5_B,
Lights::popn_lights_t::PikaButton6_R, Lights::popn_lights_t::PikaButton6_G, Lights::popn_lights_t::PikaButton6_B,
Lights::popn_lights_t::PikaButton7_R, Lights::popn_lights_t::PikaButton7_G, Lights::popn_lights_t::PikaButton7_B,
Lights::popn_lights_t::PikaButton8_R, Lights::popn_lights_t::PikaButton8_G, Lights::popn_lights_t::PikaButton8_B,
Lights::popn_lights_t::PikaButton9_R, Lights::popn_lights_t::PikaButton9_G, Lights::popn_lights_t::PikaButton9_B
};
static_assert(std::size(button_lights) == 9 * 3);
for (size_t light = 0; light < number_of_leds; light++) {
GameAPI::Lights::writeLight(
RI_MGR,
lights.at(button_lights[light]),
((uint8_t *)i_pData)[light] / 255.f);
}
}
if (tapeledutils::is_enabled() && i_CnPin > 0 && i_CnPin < std::size(TAPELED_MAPPING)) {
auto &map = TAPELED_MAPPING[i_CnPin];
const auto data_size = std::min(map.data.capacity(), (size_t)number_of_leds / 3);
// pick a color to use
const auto rgb = tapeledutils::pick_color_from_led_tape(map, (uint8_t *)i_pData, data_size);
// program the lights into API
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], rgb.r);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b);
// tape LED output over API not implemented
// for (size_t i = 0; i < data_size; i++) {
// map.data[i].r = ((uint8_t *)i_pData)[i * 3];
// map.data[i].g = ((uint8_t *)i_pData)[i * 3 + 1];
// map.data[i].b = ((uint8_t *)i_pData)[i * 3 + 2];
// }
}
}
void bi3a_hook_init() {
// avoid double init
static bool initialized = false;
if (initialized) {
return;
} else {
initialized = true;
}
// announce
log_info("bi3a_hook", "init");
// libaio-iob5.dll
const auto libaioIob5Dll = "libaio-iob5.dll";
detour::trampoline_try(libaioIob5Dll, "aioNMgrIob5_Create",
aioNMgrIob5_Create, &aioNMgrIob5_Create_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_Create",
aioIob5Bi3a_Create, &aioIob5Bi3a_Create_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_GetDeviceStatus",
aioIob5Bi3a_GetDeviceStatus, &aioIob5Bi3a_GetDeviceStatus_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetOutputData",
aioIob5Bi3a_SetOutputData, &aioIob5Bi3a_SetOutputData_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetTapeLedDataPart",
aioIob5Bi3a_SetTapeLedDataPart, &aioIob5Bi3a_SetTapeLedDataPart_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetTapeLedDataLimit",
aioIob5Bi3a_SetTapeLedDataLimit, &aioIob5Bi3a_SetTapeLedDataLimit_orig);
// libaio.dll
const auto libaioDll = "libaio.dll";
detour::trampoline_try(libaioDll, "?Open@AIO_SCI_COMM@@SA?AU?$AC_ERESULT_T@PEAVAIO_SCI_COMM@@@@PEBD0AEBUSETTING@1@I@Z",
aioSciComm_Open, &aioSciComm_Open_orig);
detour::trampoline_try(libaioDll, "aioSci_GetCommStatus",
aioSci_GetCommStatus, &aioSci_GetCommStatus_orig);
detour::trampoline_try(libaioDll, "aioSci_Destroy",
aioSci_Destroy, &aioSci_Destroy_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_Destroy",
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_GetState",
aioNodeMgr_GetState, &aioNodeMgr_GetState_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_IsReady",
aioNodeMgr_IsReady, &aioNodeMgr_IsReady_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_IsError",
aioNodeMgr_IsError, &aioNodeMgr_IsError_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_Destroy",
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_GetState",
aioNodeCtl_GetState, &aioNodeCtl_GetState_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_IsReady",
aioNodeCtl_IsReady, &aioNodeCtl_IsReady_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_UpdateDevicesStatus",
aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig);
}
}
#endif
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#if SPICE64 && !SPICE_XP
namespace games::popn {
void bi3a_hook_init();
}
#endif
+128 -78
View File
@@ -11,37 +11,7 @@ std::vector<Button> &games::popn::get_buttons() {
"Service", "Service",
"Test", "Test",
"Coin Mech", "Coin Mech",
"Button 1", "Headphones",
"Button 2",
"Button 3",
"Button 4",
"Button 5",
"Button 6",
"Button 7",
"Button 8",
"Button 9"
);
}
return buttons;
}
std::string games::popn::get_buttons_help() {
// keep to max 100 characters wide
return
" 2 4 6 8 \n"
" 1 3 5 7 9 "
;
}
std::vector<Light> &games::popn::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Pop'n Music");
GameAPI::Lights::sortLights(
&lights,
"Button 1", "Button 1",
"Button 2", "Button 2",
"Button 3", "Button 3",
@@ -51,55 +21,135 @@ std::vector<Light> &games::popn::get_lights() {
"Button 7", "Button 7",
"Button 8", "Button 8",
"Button 9", "Button 9",
"Top LED 1", "Red Pop-Kun",
"Top LED 2", "Blue Pop-Kun"
"Top LED 3",
"Top LED 4",
"Top LED 5",
"Top LED 6",
"Top LED 7",
"Top LED 8",
"Top LED 9",
"Top LED 10",
"Top LED 11",
"Top LED 12",
"Top LED 13",
"Top LED 14",
"Top LED 15",
"Top LED 16",
"Top LED 17",
"Top LED 18",
"Top LED 19",
"Top LED 20",
"Top LED 21",
"Top LED 22",
"Top LED 23",
"Top LED 24",
"Top LED 25",
"Top LED 26",
"Top LED 27",
"Top LED 28",
"Top LED 29",
"Top LED 30",
"Top LED 31",
"Top LED 32",
"Top LED R",
"Top LED G",
"Top LED B",
"Hi Lamp 1",
"Hi Lamp 2",
"Hi Lamp 3",
"Hi Lamp 4",
"Hi Lamp 5",
"Left Lamp 1",
"Left Lamp 2",
"Right Lamp 1",
"Right Lamp 2",
"Woofer LED R",
"Woofer LED G",
"Woofer LED B"
); );
} }
return buttons;
}
std::string games::popn::get_buttons_help() {
// keep to max 100 characters wide
return
"RED 2 4 6 8 BLUE\n"
" 1 3 5 7 9"
;
}
std::vector<Light> &games::popn::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Pop'n Music");
GameAPI::Lights::sortLightsWithCategory(&lights, {
{"Common", "Button 1"},
{"Common", "Button 2"},
{"Common", "Button 3"},
{"Common", "Button 4"},
{"Common", "Button 5"},
{"Common", "Button 6"},
{"Common", "Button 7"},
{"Common", "Button 8"},
{"Common", "Button 9"},
{"SD", "Hi Lamp 1"},
{"SD", "Hi Lamp 2"},
{"SD", "Hi Lamp 3"},
{"SD", "Hi Lamp 4"},
{"SD", "Hi Lamp 5"},
{"SD", "Left Lamp 1"},
{"SD", "Left Lamp 2"},
{"SD", "Right Lamp 1"},
{"SD", "Right Lamp 2"},
{"HD", "Top LED 1"},
{"HD", "Top LED 2"},
{"HD", "Top LED 3"},
{"HD", "Top LED 4"},
{"HD", "Top LED 5"},
{"HD", "Top LED 6"},
{"HD", "Top LED 7"},
{"HD", "Top LED 8"},
{"HD", "Top LED 9"},
{"HD", "Top LED 10"},
{"HD", "Top LED 11"},
{"HD", "Top LED 12"},
{"HD", "Top LED 13"},
{"HD", "Top LED 14"},
{"HD", "Top LED 15"},
{"HD", "Top LED 16"},
{"HD", "Top LED 17"},
{"HD", "Top LED 18"},
{"HD", "Top LED 19"},
{"HD", "Top LED 20"},
{"HD", "Top LED 21"},
{"HD", "Top LED 22"},
{"HD", "Top LED 23"},
{"HD", "Top LED 24"},
{"HD", "Top LED 25"},
{"HD", "Top LED 26"},
{"HD", "Top LED 27"},
{"HD", "Top LED 28"},
{"HD", "Top LED 29"},
{"HD", "Top LED 30"},
{"HD", "Top LED 31"},
{"HD", "Top LED 32"},
{"HD/Pika", "Top LED R"},
{"HD/Pika", "Top LED G"},
{"HD/Pika", "Top LED B"},
{"HD/Pika", "Woofer LED R"},
{"HD/Pika", "Woofer LED G"},
{"HD/Pika", "Woofer LED B"},
{"Pika", "Button 1 R"},
{"Pika", "Button 1 G"},
{"Pika", "Button 1 B"},
{"Pika", "Button 2 R"},
{"Pika", "Button 2 G"},
{"Pika", "Button 2 B"},
{"Pika", "Button 3 R"},
{"Pika", "Button 3 G"},
{"Pika", "Button 3 B"},
{"Pika", "Button 4 R"},
{"Pika", "Button 4 G"},
{"Pika", "Button 4 B"},
{"Pika", "Button 5 R"},
{"Pika", "Button 5 G"},
{"Pika", "Button 5 B"},
{"Pika", "Button 6 R"},
{"Pika", "Button 6 G"},
{"Pika", "Button 6 B"},
{"Pika", "Button 7 R"},
{"Pika", "Button 7 G"},
{"Pika", "Button 7 B"},
{"Pika", "Button 8 R"},
{"Pika", "Button 8 G"},
{"Pika", "Button 8 B"},
{"Pika", "Button 9 R"},
{"Pika", "Button 9 G"},
{"Pika", "Button 9 B"},
{"Pika", "Red Pop-Kun R"},
{"Pika", "Red Pop-Kun G"},
{"Pika", "Red Pop-Kun B"},
{"Pika", "Blue Pop-Kun R"},
{"Pika", "Blue Pop-Kun G"},
{"Pika", "Blue Pop-Kun B"},
{"Pika", "IC Card R"},
{"Pika", "IC Card G"},
{"Pika", "IC Card B"},
{"Pika", "Speakers R"},
{"Pika", "Speakers G"},
{"Pika", "Speakers B"},
{"Pika", "Monitor R"},
{"Pika", "Monitor G"},
{"Pika", "Monitor B"},
{"Pika", "Control Panel R"},
{"Pika", "Control Panel G"},
{"Pika", "Control Panel B"},
{"Pika", "Cabinet R"},
{"Pika", "Cabinet G"},
{"Pika", "Cabinet B"},
});
}
return lights; return lights;
} }
+62 -11
View File
@@ -11,6 +11,7 @@ namespace games::popn {
Service, Service,
Test, Test,
CoinMech, CoinMech,
Headphone,
Button1, Button1,
Button2, Button2,
Button3, Button3,
@@ -20,12 +21,14 @@ namespace games::popn {
Button7, Button7,
Button8, Button8,
Button9, Button9,
RedPop,
BluePop
}; };
} }
// all lights in correct order // all lights in correct order
namespace Lights { namespace Lights {
enum { typedef enum {
Button1, Button1,
Button2, Button2,
Button3, Button3,
@@ -35,6 +38,15 @@ namespace games::popn {
Button7, Button7,
Button8, Button8,
Button9, Button9,
HiLamp1,
HiLamp2,
HiLamp3,
HiLamp4,
HiLamp5,
LeftLamp1,
LeftLamp2,
RightLamp1,
RightLamp2,
TopLED1, TopLED1,
TopLED2, TopLED2,
TopLED3, TopLED3,
@@ -70,19 +82,58 @@ namespace games::popn {
TopLED_R, TopLED_R,
TopLED_G, TopLED_G,
TopLED_B, TopLED_B,
HiLamp1,
HiLamp2,
HiLamp3,
HiLamp4,
HiLamp5,
LeftLamp1,
LeftLamp2,
RightLamp1,
RightLamp2,
WooferLED_R, WooferLED_R,
WooferLED_G, WooferLED_G,
WooferLED_B, WooferLED_B,
}; PikaButton1_R,
PikaButton1_G,
PikaButton1_B,
PikaButton2_R,
PikaButton2_G,
PikaButton2_B,
PikaButton3_R,
PikaButton3_G,
PikaButton3_B,
PikaButton4_R,
PikaButton4_G,
PikaButton4_B,
PikaButton5_R,
PikaButton5_G,
PikaButton5_B,
PikaButton6_R,
PikaButton6_G,
PikaButton6_B,
PikaButton7_R,
PikaButton7_G,
PikaButton7_B,
PikaButton8_R,
PikaButton8_G,
PikaButton8_B,
PikaButton9_R,
PikaButton9_G,
PikaButton9_B,
RedPopKun_R,
RedPopKun_G,
RedPopKun_B,
BluePopKun_R,
BluePopKun_G,
BluePopKun_B,
IC_Card_R,
IC_Card_G,
IC_Card_B,
Speaker_R,
Speaker_G,
Speaker_B,
Monitor_R,
Monitor_G,
Monitor_B,
ControlPanel_R,
ControlPanel_G,
ControlPanel_B,
Cabinet_R,
Cabinet_G,
Cabinet_B,
} popn_lights_t;
} }
// getters // getters
+409
View File
@@ -1,7 +1,9 @@
#include "avs/game.h" #include "avs/game.h"
#include "popn.h" #include "popn.h"
#include "bi3a_hook.h"
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include "hooks/graphics/graphics.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/fileutils.h" #include "util/fileutils.h"
@@ -9,14 +11,327 @@
#include "util/utils.h" #include "util/utils.h"
#include "cfg/button.h" #include "cfg/button.h"
#include "cfg/api.h" #include "cfg/api.h"
#include "hooks/setupapihook.h"
#include "hooks/sleephook.h" #include "hooks/sleephook.h"
#include "launcher/launcher.h" #include "launcher/launcher.h"
#include "launcher/logger.h" #include "launcher/logger.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/sysutils.h"
#include "io.h" #include "io.h"
#include "util/deferlog.h"
#include "misc/nativetouchhook.h"
#include "misc/wintouchemu.h"
namespace games::popn { namespace games::popn {
bool SHOW_PIKA_MONITOR_WARNING = false;
bool NATIVE_TOUCH = false;
#if SPICE64 && !SPICE_XP
const HMONITOR FAKE_MONITOR_HMONITOR = (HMONITOR)-1;
constexpr LONG FAKE_MONITOR_OFFSET_X_Y = -999999;
constexpr LONG FAKE_MONITOR_WIDTH = 1280;
constexpr LONG FAKE_MONITOR_HEIGHT = 800;
// pin 0 = buttons 1-3, size 9 (but it's actually triple that in reality - game passes in wrong length), values 0-0xFF
// pin 1 = left pop (red pop kun), size 16, values 0-0xFF
// pin 2 = right pop (blue pop kun), size 16, values 0-0xFF
// pin 3 = title, size 44, values 0-0xFF
// pin 4 = speaker, size 40, values 0-0x7F - U region only
// pin 5 = monitor, size 18, values 0-0x7F - U region only
// pin 6 = control panel, size 28, values 0-0x7F - U region only
// pin 7 = cabinet, size 22, values 0-0x7F - U region only
tapeledutils::tape_led TAPELED_MAPPING[POPN_TAPELED_TOTAL] = {
{
// [0] button lights: these are button lights and handled specially
0,
0,
0,
0,
0,
"Invalid"
},
{
// [1] red pop-kun
5, // 5*3; game reports 16 but make it 15
Lights::popn_lights_t::RedPopKun_R,
Lights::popn_lights_t::RedPopKun_G,
Lights::popn_lights_t::RedPopKun_B,
0xFF,
"Red Pop-Kun"
},
{
// [2] blue pop-kun
5, // 5*3; game reports 16 but make it 15
Lights::popn_lights_t::BluePopKun_R,
Lights::popn_lights_t::BluePopKun_G,
Lights::popn_lights_t::BluePopKun_B,
0xFF,
"Blue Pop-Kun"
},
{
// [3] top LED
14, // 14*3; game reports 44 but make it 42
Lights::popn_lights_t::TopLED_R,
Lights::popn_lights_t::TopLED_G,
Lights::popn_lights_t::TopLED_B,
0xFF,
"Top"
},
{
// [4] speakers
13, // 13*3; game reports 40 but make it 39
Lights::popn_lights_t::Speaker_G,
Lights::popn_lights_t::Speaker_R,
Lights::popn_lights_t::Speaker_B,
0x7F,
"Speakers"
},
{
// [5] monitor
6,
Lights::popn_lights_t::Monitor_G,
Lights::popn_lights_t::Monitor_R,
Lights::popn_lights_t::Monitor_B,
0x7F,
"Monitor"
},
{
// [6] control panel
9, // 9*3; game reports 28
Lights::popn_lights_t::ControlPanel_G,
Lights::popn_lights_t::ControlPanel_R,
Lights::popn_lights_t::ControlPanel_B,
0x7F,
"Control Panel"
},
{
// [7] Cabinet
7, // 7*3; game reports 22
Lights::popn_lights_t::Cabinet_G,
Lights::popn_lights_t::Cabinet_R,
Lights::popn_lights_t::Cabinet_B,
0x7F,
"Cabinet"
},
};
static decltype(GetDisplayConfigBufferSizes) *GetDisplayConfigBufferSizes_orig = nullptr;
static decltype(QueryDisplayConfig) *QueryDisplayConfig_orig = nullptr;
static decltype(DisplayConfigGetDeviceInfo) *DisplayConfigGetDeviceInfo_orig = nullptr;
static UINT32 pNumPathArrayElements_original = 0;
static UINT32 pNumModeInfoArrayElements_original = 0;
static
LONG
WINAPI
GetDisplayConfigBufferSizes_hook(
UINT32 Flags,
UINT32 *pNumPathArrayElements,
UINT32 *pNumModeInfoArrayElements)
{
const auto ret = GetDisplayConfigBufferSizes_orig(Flags, pNumPathArrayElements, pNumModeInfoArrayElements);
if (FAKE_SUBSCREEN_ADAPTER) {
log_misc("popn", "GetDisplayConfigBufferSizes returning fake monitor paths and modes");
pNumPathArrayElements_original = *pNumPathArrayElements;
pNumModeInfoArrayElements_original = *pNumModeInfoArrayElements;
*pNumPathArrayElements += 1;
*pNumModeInfoArrayElements += 2;
}
return ret;
}
static
LONG
WINAPI
QueryDisplayConfig_hook(
UINT32 flags,
UINT32* numPathArrayElements,
DISPLAYCONFIG_PATH_INFO* pathArray,
UINT32* numModeInfoArrayElements,
DISPLAYCONFIG_MODE_INFO* modeInfoArray,
DISPLAYCONFIG_TOPOLOGY_ID* currentTopologyId)
{
if (!FAKE_SUBSCREEN_ADAPTER) {
return QueryDisplayConfig_orig(
flags,
numPathArrayElements, pathArray,
numModeInfoArrayElements, modeInfoArray,
currentTopologyId);
}
// call original to fill in real monitor info
UINT32 num_paths = pNumPathArrayElements_original;
UINT32 num_modes = pNumModeInfoArrayElements_original;
const auto ret = QueryDisplayConfig_orig(
flags,
&num_paths, pathArray,
&num_modes, modeInfoArray,
currentTopologyId);
if (ret != ERROR_SUCCESS) {
log_warning("popn", "QueryDisplayConfig failed with error code {}", ret);
return ret;
}
log_misc("popn", "QueryDisplayConfig returning fake monitor paths and modes");
// insert a fake path
DISPLAYCONFIG_PATH_INFO *path = &pathArray[pNumPathArrayElements_original];
*path = {};
path->flags = DISPLAYCONFIG_PATH_ACTIVE;
path->sourceInfo.adapterId.HighPart = -1;
path->sourceInfo.adapterId.LowPart = -1;
path->sourceInfo.id = -1;
path->sourceInfo.modeInfoIdx = pNumModeInfoArrayElements_original;
path->sourceInfo.statusFlags = DISPLAYCONFIG_SOURCE_IN_USE;
path->targetInfo.adapterId.HighPart = -1;
path->targetInfo.adapterId.LowPart = -1;
path->targetInfo.id = -2;
path->targetInfo.modeInfoIdx = pNumModeInfoArrayElements_original + 1;
path->targetInfo.outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
path->targetInfo.rotation = DISPLAYCONFIG_ROTATION_IDENTITY;
path->targetInfo.scaling = DISPLAYCONFIG_SCALING_IDENTITY;
path->targetInfo.refreshRate.Numerator = 60000;
path->targetInfo.refreshRate.Denominator = 1000;
path->targetInfo.scanLineOrdering = DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE;
path->targetInfo.targetAvailable = true;
path->targetInfo.statusFlags = DISPLAYCONFIG_TARGET_IN_USE;
// insert fake mode source
DISPLAYCONFIG_MODE_INFO *mode_source = &modeInfoArray[pNumModeInfoArrayElements_original];
*mode_source = {};
mode_source->infoType = DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE;
mode_source->id = -1;
mode_source->adapterId.HighPart = -1;
mode_source->adapterId.LowPart = -1;
mode_source->sourceMode.width = FAKE_MONITOR_WIDTH;
mode_source->sourceMode.height = FAKE_MONITOR_HEIGHT;
mode_source->sourceMode.pixelFormat = DISPLAYCONFIG_PIXELFORMAT_32BPP;
mode_source->sourceMode.position.x = FAKE_MONITOR_OFFSET_X_Y;
mode_source->sourceMode.position.y = FAKE_MONITOR_OFFSET_X_Y;
// insert fake mode target
DISPLAYCONFIG_MODE_INFO *mode_target = &modeInfoArray[pNumModeInfoArrayElements_original+1];
*mode_target = {};
mode_target->infoType = DISPLAYCONFIG_MODE_INFO_TYPE_TARGET;
mode_target->id = -2;
mode_target->adapterId.HighPart = -1;
mode_target->adapterId.LowPart = -1;
return ret;
}
static
LONG
WINAPI
DisplayConfigGetDeviceInfo_hook(DISPLAYCONFIG_DEVICE_INFO_HEADER* requestPacket)
{
if (requestPacket == nullptr) {
return DisplayConfigGetDeviceInfo_orig(requestPacket);
}
// fake monitor
if (FAKE_SUBSCREEN_ADAPTER &&
(requestPacket->id == static_cast<UINT32>(-1) ||
requestPacket->id == static_cast<UINT32>(-2)) &&
requestPacket->adapterId.HighPart == static_cast<LONG>(-1) &&
requestPacket->adapterId.LowPart == static_cast<DWORD>(-1)) {
log_misc(
"popn",
"DisplayConfigGetDeviceInfo hook hit for fake monitor, type={}, size={}, id={}, luid={}/{}",
static_cast<int>(requestPacket->type),
requestPacket->size,
requestPacket->id,
requestPacket->adapterId.HighPart,
requestPacket->adapterId.LowPart);
if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) {
const auto target = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
target->flags.value = 0;
target->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
target->connectorInstance = 0;
wcscpy(target->monitorFriendlyDeviceName, L"Spice Fake Monitor");
wcscpy(target->monitorDevicePath, L"\\\\?\\SpiceFakeMonitor");
} else if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) {
const auto source = reinterpret_cast<DISPLAYCONFIG_SOURCE_DEVICE_NAME*>(requestPacket);
// value must match WrappedIDirect3D9::GetAdapterIdentifier
wcscpy(source->viewGdiDeviceName, L"\\\\.\\DISPLAY_SPICE_FAKE");
} else {
log_fatal(
"popn",
"unexpected device info type {} for fake monitor",
static_cast<int>(requestPacket->type));
}
return ERROR_SUCCESS;
}
const auto ret = DisplayConfigGetDeviceInfo_orig(requestPacket);
log_misc(
"popn",
"DisplayConfigGetDeviceInfo returned {}, type={}, size={}, id={}, luid={}/{}",
ret,
static_cast<int>(requestPacket->type),
requestPacket->size,
requestPacket->id,
requestPacket->adapterId.HighPart,
requestPacket->adapterId.LowPart);
if (ret == ERROR_SUCCESS) {
if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) {
const auto sourceName = reinterpret_cast<DISPLAYCONFIG_SOURCE_DEVICE_NAME*>(requestPacket);
log_misc(
"popn",
"... name={}",
ws2s(sourceName->viewGdiDeviceName));
} else if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) {
const auto targetName = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
log_misc(
"popn",
"... flags={}, outputTechnology: {}, connectorInstance={}, friendlyname={} path={}",
targetName->flags.value,
static_cast<int>(targetName->outputTechnology),
targetName->connectorInstance,
ws2s(targetName->monitorFriendlyDeviceName),
ws2s(targetName->monitorDevicePath));
// need to fix up some values for the primary monitor...
const auto monitors = sysutils::enumerate_monitors();
for (const auto& monitor : monitors) {
if (monitor.id == targetName->header.id &&
monitor.adapter_id_HighPart == targetName->header.adapterId.HighPart &&
monitor.adapter_id_LowPart == targetName->header.adapterId.LowPart) {
if (monitor.is_primary) {
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL;
targetName->connectorInstance = 2;
log_info(
"popn",
"... overriding primary monitor ({}) to pretend to be DP port #2",
monitor.display_name);
} else {
// TODO: is this what the game expects for subscreen?
// TODO: what if there are 3+ monitors?
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
targetName->connectorInstance = 0;
}
break;
}
}
}
}
return ret;
}
#endif
#if !SPICE64
static int __cdecl usbCheckAlive() { static int __cdecl usbCheckAlive() {
return 1; return 1;
} }
@@ -227,6 +542,8 @@ namespace games::popn {
return MMSYSERR_NOERROR; return MMSYSERR_NOERROR;
} }
#endif
static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out) { static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out) {
// get rid of the log spam // get rid of the log spam
@@ -249,6 +566,8 @@ namespace games::popn {
void POPNGame::pre_attach() { void POPNGame::pre_attach() {
Game::pre_attach(); Game::pre_attach();
#if !SPICE64
// only needed for older versions (19-21, inclusive) but create them anyway since 21 and above are all M39 // only needed for older versions (19-21, inclusive) but create them anyway since 21 and above are all M39
fileutils::dir_create_recursive_log("popn", "dev\\raw\\bookkeeping"); fileutils::dir_create_recursive_log("popn", "dev\\raw\\bookkeeping");
fileutils::dir_create_recursive_log("popn", "dev\\raw\\ranking"); fileutils::dir_create_recursive_log("popn", "dev\\raw\\ranking");
@@ -257,11 +576,15 @@ namespace games::popn {
// load without resolving references // load without resolving references
// makes game not trigger DLLMain which results in an error due to missing EZUSB device // makes game not trigger DLLMain which results in an error due to missing EZUSB device
LoadLibraryExW((MODULE_PATH / "ezusb.dll").c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES); LoadLibraryExW((MODULE_PATH / "ezusb.dll").c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES);
#endif
} }
void POPNGame::attach() { void POPNGame::attach() {
Game::attach(); Game::attach();
#if !SPICE64
/* /*
* Fast Boot (TM) Patch * Fast Boot (TM) Patch
* Game tries to create some directories and if it fails it will sleep for 1 second * Game tries to create some directories and if it fails it will sleep for 1 second
@@ -320,5 +643,91 @@ namespace games::popn {
detour::inline_hook((void *) usbWdtStartDone, detour::inline_hook((void *) usbWdtStartDone,
libutils::try_proc(ezusb, "?usbWdtStartDone@@YAHXZ")); libutils::try_proc(ezusb, "?usbWdtStartDone@@YAHXZ"));
} }
#endif
#if SPICE64 && !SPICE_XP
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value()) {
SHOW_PIKA_MONITOR_WARNING = true;
log_warning(
"popn",
"\n\n"
"!!! using -dxmainadapter option is NOT recommended due to known !!!\n"
"!!! compatibility issues with the game !!!\n"
"!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n"
"!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
"!!! option instead of -dxmainadapter !!!\n\n"
);
deferredlogs::defer_error_messages({
"-dxmainadapter option is NOT recommended due to known compatibility ",
" issues with the game!",
" * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay",
" use Change Main Monitor (-mainmonitor) option instead"
});
}
// monitor hook
GetDisplayConfigBufferSizes_orig =
detour::iat_try("GetDisplayConfigBufferSizes",
GetDisplayConfigBufferSizes_hook, avs::game::DLL_INSTANCE);
QueryDisplayConfig_orig =
detour::iat_try("QueryDisplayConfig",
QueryDisplayConfig_hook, avs::game::DLL_INSTANCE);
DisplayConfigGetDeviceInfo_orig =
detour::iat_try("DisplayConfigGetDeviceInfo",
DisplayConfigGetDeviceInfo_hook, avs::game::DLL_INSTANCE);
// GUID_DEVCLASS_USB = {86E0D1E0-11D0-89B0-00A0C9054129}
SETUPAPI_SETTINGS settings{};
settings.class_guid[0] = 0x86E0D1E0;
settings.class_guid[1] = 0x11D08089;
settings.class_guid[2] = 0x0008E49C;
settings.class_guid[3] = 0x731F303E;
// BIO3 - 8058 (maybe U region cabs with more lights?)
// BIO3 - 8054 (probably J region?)
// BIO2 - 8050 (need bi2x_hook, everything will work except buttons 1-9)
const char property[] = "1CCF(8058)_000";
const char property_hardwareid[] = "USB\\VID_1CCF&PID_8058&MI_00\\000";
memcpy(settings.property_devicedesc, property, sizeof(property));
memcpy(settings.property_hardwareid, property_hardwareid, sizeof(property_hardwareid));
setupapihook_init(avs::game::DLL_INSTANCE);
setupapihook_add(settings);
bi3a_hook_init();
// note: if for whatever reason you want to use BIO2, the game needs to be patched
// check popn.dll and look for static table that looks like this:
// 00000001 00000003 00000001 (button 1)
// 00000002 00000004 00000001 (button 2)
// 00000004 00000005 00000001 (button 3)
// 00000008 00000006 00000001 (button 4)
// 00000010 00000007 00000001 (button 5)
// 00000020 00000008 00000001 (button 6)
// 00000040 00000009 00000001 (button 7)
// 00000080 0000000A 00000001 (button 8)
// 00000100 0000000B 00000001 (button 9)
// set third column to 0 and it will work with BIO2
if (!GRAPHICS_WINDOWED) {
if (NATIVE_TOUCH) {
nativetouchhook::hook(avs::game::DLL_INSTANCE);
} else {
wintouchemu::FORCE = true;
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
wintouchemu::hook_title_ends("", "Main Screen", avs::game::DLL_INSTANCE);
}
}
sysutils::hook_EnumDisplayDevicesA();
#endif
} }
} }
+20
View File
@@ -1,9 +1,29 @@
#pragma once #pragma once
#include "avs/game.h"
#include "games/game.h" #include "games/game.h"
#include "util/tapeled.h"
namespace games::popn { namespace games::popn {
static inline bool is_pikapika_model() {
#if SPICE64
return (avs::game::is_model("M39") && avs::game::SPEC[0] == 'D');
#else
return false;
#endif
}
#if SPICE64
constexpr int POPN_TAPELED_TOTAL = 8;
extern tapeledutils::tape_led TAPELED_MAPPING[POPN_TAPELED_TOTAL];
#endif
extern bool SHOW_PIKA_MONITOR_WARNING;
extern bool NATIVE_TOUCH;
class POPNGame : public games::Game { class POPNGame : public games::Game {
public: public:
POPNGame(); POPNGame();
+14 -9
View File
@@ -21,6 +21,7 @@
#include "util/socd_cleaner.h" #include "util/socd_cleaner.h"
#include "util/time.h" #include "util/time.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/sysutils.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "misc/nativetouchhook.h" #include "misc/nativetouchhook.h"
#include "misc/wintouchemu.h" #include "misc/wintouchemu.h"
@@ -350,28 +351,28 @@ namespace games::sdvx {
} }
#ifdef SPICE64 // SDVX5+ specific code #ifdef SPICE64 // SDVX5+ specific code
// check -monitor + UFC mode // check -dxmainadapter + UFC mode
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && is_valkyrie_model()) { if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && is_valkyrie_model()) {
SHOW_VM_MONITOR_WARNING = true; SHOW_VM_MONITOR_WARNING = true;
log_warning( log_warning(
"sdvx", "sdvx",
"\n\n!!! using -monitor option with VM mode is NOT recommended due to !!!\n" "\n\n"
"!!! known compatibility issues with the game !!!\n" "!!! using -dxmainadapter option with Valkyrie mode is NOT !!!\n"
"!!! recommended due to known compatibility issues with the game !!!\n"
"!!! !!!\n" "!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n" "!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n" "!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n" "!!! !!!\n"
"!!! recommendation is to NOT use -monitor and instead set the !!!\n" "!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
"!!! primary monitor in Windows settings before launching the game !!!\n\n" "!!! option instead of -dxmainadapter !!!\n\n"
); );
deferredlogs::defer_error_messages({ deferredlogs::defer_error_messages({
"-monitor option is NOT recommended when running with Valkyrie mode", "-dxmainadapter option is NOT recommended when running with Valkyrie mode",
" due to known compatibility issues with the game:", " due to known compatibility issues with the game:",
" * game may launch in wrong resolution or refresh rate", " * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay", " * touch / mouse input may stop working in subscreen / overlay",
" recommended fix is to NOT use -monitor and instead set the primary", " use Change Main Monitor (-mainmonitor) option instead"
" monitor in Windows settings before launching the game"
}); });
} }
#endif #endif
@@ -391,7 +392,7 @@ namespace games::sdvx {
} }
} }
} }
void SDVXGame::attach() { void SDVXGame::attach() {
Game::attach(); Game::attach();
@@ -466,6 +467,7 @@ namespace games::sdvx {
ENABLE_COM_PORT_SCAN_HOOK = true; ENABLE_COM_PORT_SCAN_HOOK = true;
} }
} }
#endif #endif
// ASIO device hook // ASIO device hook
@@ -501,6 +503,9 @@ namespace games::sdvx {
// remove log spam // remove log spam
logger::hook_add(sdvx64_spam_remover, nullptr); logger::hook_add(sdvx64_spam_remover, nullptr);
if (is_valkyrie_model()) {
sysutils::hook_EnumDisplayDevicesA();
}
#endif #endif
} }
@@ -15,6 +15,7 @@
#include "cfg/screen_resize.h" #include "cfg/screen_resize.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "games/sdvx/sdvx.h" #include "games/sdvx/sdvx.h"
#include "games/mfc/mfc.h" #include "games/mfc/mfc.h"
#include "games/io.h" #include "games/io.h"
@@ -462,7 +463,20 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::RegisterSoftwareDevice(void *pIniti
} }
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterCount() { UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterCount() {
return pReal->GetAdapterCount(); UINT result = pReal->GetAdapterCount();
if (!FAKE_SUBSCREEN_ADAPTER && games::popn::is_pikapika_model() && result == 1) {
FAKE_SUBSCREEN_ADAPTER = true;
log_info(
"graphics::d3d9",
"GetAdapterCount returned 1, popn needs 2 adapters - enabliing fake submonitor mode");
}
if (FAKE_SUBSCREEN_ADAPTER) {
return 2;
}
return result;
} }
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier( HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
@@ -470,10 +484,26 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
DWORD Flags, DWORD Flags,
D3DADAPTER_IDENTIFIER9 *pIdentifier) D3DADAPTER_IDENTIFIER9 *pIdentifier)
{ {
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1 && pIdentifier) {
*pIdentifier = {};
strcpy(pIdentifier->DeviceName, "\\\\.\\DISPLAY_SPICE_FAKE");
log_misc(
"graphics::d3d9",
"GetAdapterIdentifier called for fake subscreen adapter 1: {}",
pIdentifier->DeviceName);
return S_OK;
}
CHECK_RESULT(pReal->GetAdapterIdentifier(Adapter, Flags, pIdentifier)); CHECK_RESULT(pReal->GetAdapterIdentifier(Adapter, Flags, pIdentifier));
} }
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) { UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) {
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
log_misc("graphics::d3d9", "GetAdapterModeCount called for fake subscreen adapter");
return 1;
}
return pReal->GetAdapterModeCount(Adapter, Format); return pReal->GetAdapterModeCount(Adapter, Format);
} }
@@ -483,6 +513,19 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
UINT Mode, UINT Mode,
D3DDISPLAYMODE *pMode) D3DDISPLAYMODE *pMode)
{ {
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
log_misc("graphics::d3d9", "EnumAdapterModes called for fake subscreen adapter");
if (Mode == 0 && pMode) {
pMode->Width = 1280;
pMode->Height = 800;
pMode->RefreshRate = 60;
pMode->Format = D3DFMT_X8R8G8B8;
return S_OK;
} else {
return D3DERR_INVALIDCALL;
}
}
HRESULT ret = pReal->EnumAdapterModes(Adapter, Format, Mode, pMode); HRESULT ret = pReal->EnumAdapterModes(Adapter, Format, Mode, pMode);
if (SUCCEEDED(ret) && pMode) { if (SUCCEEDED(ret) && pMode) {
@@ -579,6 +622,11 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceFormat(
D3DRESOURCETYPE RType, D3DRESOURCETYPE RType,
D3DFORMAT CheckFormat) D3DFORMAT CheckFormat)
{ {
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
log_misc("graphics::d3d9", "CheckDeviceFormat called for fake subscreen adapter");
return D3D_OK;
}
CHECK_RESULT(pReal->CheckDeviceFormat(Adapter, DeviceType, AdapterFormat, Usage, RType, CheckFormat)); CHECK_RESULT(pReal->CheckDeviceFormat(Adapter, DeviceType, AdapterFormat, Usage, RType, CheckFormat));
} }
@@ -660,7 +708,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
} }
// in windowed mode, LDJ will always launch two windows, no special handling needed here // in windowed mode, LDJ will always launch two windows, no special handling needed here
} else if (avs::game::is_model("KFC")) { } else if (avs::game::is_model("KFC")) {
if (GRAPHICS_WINDOWED & GRAPHICS_PREVENT_SECONDARY_WINDOW) { if (GRAPHICS_WINDOWED && GRAPHICS_PREVENT_SECONDARY_WINDOW) {
// user wants windowed mode but does not want subscreen at all // user wants windowed mode but does not want subscreen at all
pCaps->NumberOfAdaptersInGroup = 1; pCaps->NumberOfAdaptersInGroup = 1;
} else { } else {
@@ -675,6 +723,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
} else if (GRAPHICS_FORCE_SINGLE_ADAPTER) { } else if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
pCaps->NumberOfAdaptersInGroup = 1; pCaps->NumberOfAdaptersInGroup = 1;
} }
} else if (games::popn::is_pikapika_model()) {
pCaps->NumberOfAdaptersInGroup = 2;
} }
return ret; return ret;
@@ -1021,7 +1071,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
// //
// note from MSDN: `pFullscreenDisplayMode` must be NULL for windowed mode. // note from MSDN: `pFullscreenDisplayMode` must be NULL for windowed mode.
if (GRAPHICS_WINDOWED) { if (GRAPHICS_WINDOWED) {
if (avs::game::is_model({"LDJ", "KFC"}) && (BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE)) { if (avs::game::is_model({"LDJ", "KFC", "M39"}) &&
(BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
log_misc("graphics::d3d9", "disabling adapter group device in windowed mode"); log_misc("graphics::d3d9", "disabling adapter group device in windowed mode");
D3D9_BEHAVIOR_DISABLE |= D3DCREATE_ADAPTERGROUP_DEVICE; D3D9_BEHAVIOR_DISABLE |= D3DCREATE_ADAPTERGROUP_DEVICE;
@@ -1093,7 +1144,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface); *ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
// initialize sub screen if IIDX/SDVX requested a multi-head context // initialize sub screen if IIDX/SDVX requested a multi-head context
if (avs::game::is_model({"LDJ", "KFC"}) && (orig_behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE)) { if (avs::game::is_model({"LDJ", "KFC", "M39"}) &&
(orig_behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
graphics_d3d9_ldj_init_sub_screen(*ppReturnedDeviceInterface, &pPresentationParameters[1]); graphics_d3d9_ldj_init_sub_screen(*ppReturnedDeviceInterface, &pPresentationParameters[1]);
} }
} }
@@ -1132,8 +1184,6 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
*/ */
if (GRAPHICS_WINDOWED) { if (GRAPHICS_WINDOWED) {
log_info("graphics::d3d9", "creating additional swap chain");
present_params->Windowed = true; present_params->Windowed = true;
present_params->FullScreen_RefreshRateInHz = 0; present_params->FullScreen_RefreshRateInHz = 0;
@@ -1142,18 +1192,23 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN); hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN);
if (FAILED(hr)) { if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to create additional swap chain, hr={}", FMT_HRESULT(hr)); log_warning("graphics::d3d9", "failed to create additional swap chain, hr={}", FMT_HRESULT(hr));
} else {
log_info("graphics::d3d9", "created additional swap chain for windowed mode");
} }
} else { } else {
hr = device->GetSwapChain(1, &SUB_SWAP_CHAIN); hr = device->GetSwapChain(1, &SUB_SWAP_CHAIN);
if (FAILED(hr)) { if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to acquire fullscreen sub swap chain, hr={}", FMT_HRESULT(hr)); log_warning("graphics::d3d9", "failed to acquire fullscreen sub swap chain, hr={}", FMT_HRESULT(hr));
} else { } else {
log_info("graphics::d3d9", "acquired fullscreen sub swap chain");
return; return;
} }
hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN); hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN);
if (FAILED(hr)) { if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to get additional swap chain, hr={}", FMT_HRESULT(hr)); log_warning("graphics::d3d9", "failed to get additional swap chain, hr={}", FMT_HRESULT(hr));
} else {
log_info("graphics::d3d9", "created additional swap chain for fullscreen mode");
} }
} }
} }
@@ -1196,7 +1251,10 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
if (SUB_SWAP_CHAIN != nullptr) { if (SUB_SWAP_CHAIN != nullptr) {
wintouchemu::update(); wintouchemu::update();
if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW) { // newer versions of exceed gear needs SUBSCREEN_FORCE_REDRAW
// (when enabled on older versions of EG, you end up with graphical glitches on the subscreen
// early versions of popn HC needs this as well, otherwise the subscreen doesn't update at all
if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW || games::popn::is_pikapika_model()) {
SUB_SWAP_CHAIN->Present(nullptr, nullptr, nullptr, nullptr, 0); SUB_SWAP_CHAIN->Present(nullptr, nullptr, nullptr, nullptr, 0);
} }
} }
@@ -1396,7 +1454,8 @@ void graphics_d3d9_on_present(
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE; const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
const bool is_gfdm_arena = const bool is_gfdm_arena =
games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW; games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW;
if (is_vm || is_tdj || is_gfdm_arena) { const bool is_pika = games::popn::is_pikapika_model();
if (is_vm || is_tdj || is_gfdm_arena || is_pika) {
graphics_d3d9_ldj_on_present(wrapped_device); graphics_d3d9_ldj_on_present(wrapped_device);
} }
@@ -251,7 +251,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain); HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain);
bool create_swap_chain = false; bool create_swap_chain = false;
if (avs::game::is_model({"LDJ", "KFC"})) { if (avs::game::is_model({"LDJ", "KFC", "M39"})) {
create_swap_chain = true; create_swap_chain = true;
} else if (games::gitadora::is_arena_model() && } else if (games::gitadora::is_arena_model() &&
games::gitadora::ARENA_SINGLE_WINDOW && games::gitadora::ARENA_SINGLE_WINDOW &&
@@ -305,7 +305,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
} }
bool swap = false; bool swap = false;
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC"})) { if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC", "M39"})) {
swap = true; swap = true;
} }
if (games::gitadora::is_arena_model() && if (games::gitadora::is_arena_model() &&
@@ -344,7 +344,7 @@ UINT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetNumberOfSwapChains() {
UINT n = pReal->GetNumberOfSwapChains(); UINT n = pReal->GetNumberOfSwapChains();
if (sub_swapchain && avs::game::is_model({"LDJ", "KFC"})) { if (sub_swapchain && avs::game::is_model({"LDJ", "KFC", "M39"})) {
n += 1; n += 1;
} }
+49 -9
View File
@@ -1,3 +1,6 @@
// GetDisplayConfigBufferSizes etc is Vista+
#define _WIN32_WINNT 0x0601
#include <initguid.h> #include <initguid.h>
#include "graphics.h" #include "graphics.h"
@@ -13,6 +16,7 @@
#include "games/ddr/ddr.h" #include "games/ddr/ddr.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/backends/d3d9/d3d9_backend.h" #include "hooks/graphics/backends/d3d9/d3d9_backend.h"
#include "launcher/shutdown.h" #include "launcher/shutdown.h"
#include "overlay/overlay.h" #include "overlay/overlay.h"
@@ -35,6 +39,8 @@ struct CaptureData {
HWND TDJ_SUBSCREEN_WINDOW = nullptr; HWND TDJ_SUBSCREEN_WINDOW = nullptr;
HWND SDVX_SUBSCREEN_WINDOW = nullptr; HWND SDVX_SUBSCREEN_WINDOW = nullptr;
HWND GFDM_SUBSCREEN_WINDOW = nullptr; HWND GFDM_SUBSCREEN_WINDOW = nullptr;
HWND POPN_SUBSCREEN_WINDOW = nullptr;
bool FAKE_SUBSCREEN_ADAPTER = false;
// icon // icon
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON)); static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
@@ -232,9 +238,9 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
// This allows touches received on subscreen window to be translated correctly. // This allows touches received on subscreen window to be translated correctly.
update_spicetouch_window_dimensions(hWnd); update_spicetouch_window_dimensions(hWnd);
// log_misc( // log_misc(
// "graphics", "detected window change ({}x{} @ {}, {}), updating touch coord-space to match", // "graphics", "detected window change ({}x{} @ {}, {}), updating touch coord-space to match",
// SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT, SPICETOUCH_TOUCH_X, SPICETOUCH_TOUCH_Y); // SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT, SPICETOUCH_TOUCH_X, SPICETOUCH_TOUCH_Y);
// Update SPICETOUCH window if present // Update SPICETOUCH window if present
if (SPICETOUCH_TOUCH_HWND) { if (SPICETOUCH_TOUCH_HWND) {
SetWindowPos( SetWindowPos(
@@ -331,17 +337,18 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
bool is_tdj_sub_window = avs::game::is_model("LDJ") && window_name.ends_with(" sub"); bool is_tdj_sub_window = avs::game::is_model("LDJ") && window_name.ends_with(" sub");
bool is_sdvx_sub_window = avs::game::is_model("KFC") && window_name.ends_with(" Sub Screen"); bool is_sdvx_sub_window = avs::game::is_model("KFC") && window_name.ends_with(" Sub Screen");
bool is_popn_sub_window = avs::game::is_model("M39") && window_name.ends_with("Sub Screen");
bool is_gfdm_sub_window = games::gitadora::is_arena_model() && window_name.ends_with("SMALL"); bool is_gfdm_sub_window = games::gitadora::is_arena_model() && window_name.ends_with("SMALL");
// hide maximize button (prevent misaligned touches) // hide maximize button (prevent misaligned touches)
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window) { if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window || is_popn_sub_window) {
dwStyle &= ~(WS_MAXIMIZEBOX); dwStyle &= ~(WS_MAXIMIZEBOX);
} }
// mouse clicks become misaligned when resized // mouse clicks become misaligned when resized
if (is_gfdm_sub_window) { if (is_gfdm_sub_window) {
dwStyle &= ~(WS_SIZEBOX); dwStyle &= ~(WS_SIZEBOX);
} }
if ((is_tdj_sub_window || is_sdvx_sub_window) && GRAPHICS_WSUB_BORDERLESS) { if ((is_tdj_sub_window || is_sdvx_sub_window || is_popn_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
dwStyle &= ~(WS_OVERLAPPEDWINDOW); dwStyle &= ~(WS_OVERLAPPEDWINDOW);
} }
@@ -363,6 +370,10 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
} }
} }
if (GRAPHICS_WINDOWED) {
graphics_window_check_bounds_before_creation(x, y, nWidth, nHeight);
}
// call original // call original
HWND result = CreateWindowExA_orig(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, HWND result = CreateWindowExA_orig(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
hWndParent, hMenu, hInstance, lpParam); hWndParent, hMenu, hInstance, lpParam);
@@ -371,7 +382,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
if (is_tdj_sub_window) { if (is_tdj_sub_window) {
// TDJ windowed mode: remember the subscreen window handle for later // TDJ windowed mode: remember the subscreen window handle for later
TDJ_SUBSCREEN_WINDOW = result; TDJ_SUBSCREEN_WINDOW = result;
// hook for preventing the closing of subscreen window // hook for preventing the closing of subscreen window
if (GRAPHICS_IIDX_WSUB) { if (GRAPHICS_IIDX_WSUB) {
graphics_hook_subscreen_window(TDJ_SUBSCREEN_WINDOW); graphics_hook_subscreen_window(TDJ_SUBSCREEN_WINDOW);
@@ -390,6 +401,13 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW); graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
} }
if (is_popn_sub_window) {
POPN_SUBSCREEN_WINDOW = result;
if (!GRAPHICS_PREVENT_SECONDARY_WINDOW) {
graphics_hook_subscreen_window(POPN_SUBSCREEN_WINDOW);
}
}
disable_touch_indicators(result); disable_touch_indicators(result);
log_misc( log_misc(
"graphics", "graphics",
@@ -458,6 +476,18 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
} }
} }
// FTT
if (avs::game::is_model("MMD")) {
// set window name
if (!lpWindowName) {
lpWindowName = L"Future TomTom";
}
}
if (GRAPHICS_WINDOWED) {
graphics_window_check_bounds_before_creation(x, y, nWidth, nHeight);
}
// call original // call original
HWND result = CreateWindowExW_orig( HWND result = CreateWindowExW_orig(
dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
@@ -530,7 +560,7 @@ static BOOL WINAPI MoveWindow_hook(HWND hWnd, int X, int Y, int nWidth, int nHei
if (GRAPHICS_IIDX_WSUB) { if (GRAPHICS_IIDX_WSUB) {
// (Experimental) Show subscreen in windowed mode // (Experimental) Show subscreen in windowed mode
graphics_load_windowed_subscreen_parameters(); graphics_load_windowed_subscreen_parameters();
RECT rect {}; RECT rect {};
DWORD dwStyle; DWORD dwStyle;
@@ -653,6 +683,13 @@ static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
return true; return true;
} }
if (games::popn::is_pikapika_model() &&
GRAPHICS_PREVENT_SECONDARY_WINDOW &&
hWnd == POPN_SUBSCREEN_WINDOW) {
log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd));
return true;
}
// call original // call original
return ShowWindow_orig(hWnd, nCmdShow); return ShowWindow_orig(hWnd, nCmdShow);
} }
@@ -895,6 +932,9 @@ void graphics_hook_subscreen_window(HWND hWnd) {
if (GRAPHICS_WSUB_ALWAYS_ON_TOP) { if (GRAPHICS_WSUB_ALWAYS_ON_TOP) {
graphics_update_z_order(hWnd, true); graphics_update_z_order(hWnd, true);
} }
if (GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS) {
graphics_set_corner_preference(hWnd, true);
}
} }
void graphics_screens_register(int screen) { void graphics_screens_register(int screen) {
@@ -1085,7 +1125,7 @@ static std::string get_dmdo_string(DWORD dmdo) {
} }
void change_primary_monitor(const std::string &monitor_name) { void change_primary_monitor(const std::string &monitor_name) {
log_misc("graphics", "try changing primary monitor to {}...", monitor_name); log_misc("graphics", "try changing primary monitor to {}...", monitor_name);
// for WinXP, since these are Vista+ or 7+ APIs // for WinXP, since these are Vista+ or 7+ APIs
const auto user32 = LoadLibraryA("user32.dll"); const auto user32 = LoadLibraryA("user32.dll");
@@ -1116,7 +1156,7 @@ void change_primary_monitor(const std::string &monitor_name) {
std::vector<DISPLAYCONFIG_PATH_INFO> paths; std::vector<DISPLAYCONFIG_PATH_INFO> paths;
std::vector<DISPLAYCONFIG_MODE_INFO> modes; std::vector<DISPLAYCONFIG_MODE_INFO> modes;
bool succeeded = false; bool succeeded = false;
// in a retry loop, try to query for display config // in a retry loop, try to query for display config
// retry loop is needed because it can fail with ERROR_INSUFFICIENT_BUFFER // retry loop is needed because it can fail with ERROR_INSUFFICIENT_BUFFER
for (int attempt = 0; attempt < 5; ++attempt) { for (int attempt = 0; attempt < 5; ++attempt) {
@@ -1323,4 +1363,4 @@ void update_monitor_on_boot(std::optional<graphics_orientation> target_orientati
// sleep for a little bit after changing monitor settings to delay game launch // sleep for a little bit after changing monitor settings to delay game launch
Sleep(1000); Sleep(1000);
} }
+9 -1
View File
@@ -7,6 +7,9 @@
#include <windows.h> #include <windows.h>
#include <d3d9.h> #include <d3d9.h>
#if !SPICE_XP
#include <dwmapi.h>
#endif
#include "external/toojpeg/toojpeg.h" #include "external/toojpeg/toojpeg.h"
@@ -50,6 +53,7 @@ extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
extern std::optional<std::string> GRAPHICS_WINDOW_POS; extern std::optional<std::string> GRAPHICS_WINDOW_POS;
extern bool GRAPHICS_WINDOW_ALWAYS_ON_TOP; extern bool GRAPHICS_WINDOW_ALWAYS_ON_TOP;
extern bool GRAPHICS_WINDOW_BACKBUFFER_SCALE; extern bool GRAPHICS_WINDOW_BACKBUFFER_SCALE;
extern bool GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS;
extern std::optional<HWND> GRAPHICS_HOOKED_WINDOW; extern std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
extern bool GRAPHICS_IIDX_WSUB; extern bool GRAPHICS_IIDX_WSUB;
@@ -63,9 +67,11 @@ extern bool GRAPHICS_WSUB_BORDERLESS;
extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP; extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP;
extern HWND TDJ_SUBSCREEN_WINDOW; extern HWND TDJ_SUBSCREEN_WINDOW;
extern HWND SDVX_SUBSCREEN_WINDOW; extern HWND SDVX_SUBSCREEN_WINDOW;
extern HWND POPN_SUBSCREEN_WINDOW;
extern HWND GFDM_SUBSCREEN_WINDOW; extern HWND GFDM_SUBSCREEN_WINDOW;
extern bool SUBSCREEN_FORCE_REDRAW; extern bool SUBSCREEN_FORCE_REDRAW;
extern bool FAKE_SUBSCREEN_ADAPTER;
// settings // settings
extern std::string GRAPHICS_DEVICEID; extern std::string GRAPHICS_DEVICEID;
@@ -99,14 +105,16 @@ std::string graphics_screenshot_genpath();
// graphics_windowed.cpp // graphics_windowed.cpp
void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void graphics_capture_initial_window(HWND hWnd); void graphics_capture_initial_window(HWND hWnd);
void graphics_update_window_style(HWND hWnd); void graphics_update_window_style(HWND hWnd, bool force_update=false);
void graphics_update_z_order(HWND hWnd, bool always_on_top); void graphics_update_z_order(HWND hWnd, bool always_on_top);
void graphics_set_corner_preference(HWND hWnd, bool disable);
void graphics_move_resize_window(HWND hWnd); void graphics_move_resize_window(HWND hWnd);
bool graphics_window_options_breaks_game(); bool graphics_window_options_breaks_game();
bool graphics_window_decoration_change_crashes_game(); bool graphics_window_decoration_change_crashes_game();
bool graphics_window_resize_breaks_game(); bool graphics_window_resize_breaks_game();
bool graphics_window_move_and_resize_breaks_game(); bool graphics_window_move_and_resize_breaks_game();
void graphics_load_windowed_subscreen_parameters(); void graphics_load_windowed_subscreen_parameters();
void graphics_window_check_bounds_before_creation(int &x, int &y, const int width, const int height);
void change_primary_monitor(const std::string &monitor_name); void change_primary_monitor(const std::string &monitor_name);
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate); void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate);
@@ -24,6 +24,7 @@ std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
std::optional<std::string> GRAPHICS_WINDOW_POS; std::optional<std::string> GRAPHICS_WINDOW_POS;
bool GRAPHICS_WINDOW_ALWAYS_ON_TOP = false; bool GRAPHICS_WINDOW_ALWAYS_ON_TOP = false;
bool GRAPHICS_WINDOW_BACKBUFFER_SCALE = false; bool GRAPHICS_WINDOW_BACKBUFFER_SCALE = false;
bool GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS = false;
std::optional<HWND> GRAPHICS_HOOKED_WINDOW; std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed // IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
@@ -121,18 +122,21 @@ void graphics_capture_initial_window(HWND hWnd) {
cfg::SCREENRESIZE->client_height); cfg::SCREENRESIZE->client_height);
graphics_move_resize_window(hWnd); graphics_move_resize_window(hWnd);
} }
// ddr hangs when window frame doesn't have overlapped
if (cfg::SCREENRESIZE->window_decoration != cfg::WindowDecorationMode::Default) { if (cfg::SCREENRESIZE->window_decoration != cfg::WindowDecorationMode::Default) {
log_info( log_info(
"graphics-windowed", "[{}] change window style - decoration: {}", "graphics-windowed", "[{}] change window style - decoration: {}",
fmt::ptr(hWnd), fmt::ptr(hWnd),
cfg::SCREENRESIZE->window_decoration); cfg::SCREENRESIZE->window_decoration);
graphics_update_window_style(hWnd); graphics_update_window_style(hWnd, true); // force
} }
if (cfg::SCREENRESIZE->window_always_on_top) { if (cfg::SCREENRESIZE->window_always_on_top) {
log_info("graphics-windowed", "[{}] change window z-order - always on top", fmt::ptr(hWnd)); log_info("graphics-windowed", "[{}] change window z-order - always on top", fmt::ptr(hWnd));
graphics_update_z_order(hWnd, true); graphics_update_z_order(hWnd, true);
} }
if (cfg::SCREENRESIZE->window_disable_round_corners) {
log_info("graphics-windowed", "[{}] disabling rounded corners", fmt::ptr(hWnd));
graphics_set_corner_preference(hWnd, true);
}
// ensure spicetouch coordinates are initialized // ensure spicetouch coordinates are initialized
update_spicetouch_window_dimensions(hWnd); update_spicetouch_window_dimensions(hWnd);
@@ -182,6 +186,10 @@ void graphics_load_windowed_parameters() {
cfg::SCREENRESIZE->window_always_on_top = true; cfg::SCREENRESIZE->window_always_on_top = true;
} }
if (GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS) {
cfg::SCREENRESIZE->window_disable_round_corners = true;
}
log_debug("graphics-windowed", "graphics_load_windowed_parameters returned"); log_debug("graphics-windowed", "graphics_load_windowed_parameters returned");
} }
@@ -385,11 +393,11 @@ void graphics_wm_sizing_aspect_ratio(int edge, RECT& rect) {
log_debug("graphics-windowed", "graphics_wm_sizing_aspect_ratio returned"); log_debug("graphics-windowed", "graphics_wm_sizing_aspect_ratio returned");
} }
void graphics_update_window_style(HWND hWnd) { void graphics_update_window_style(HWND hWnd, bool force_update) {
if (!GRAPHICS_WINDOWED) { if (!GRAPHICS_WINDOWED) {
return; return;
} }
if (graphics_window_decoration_change_crashes_game()) { if (!force_update && graphics_window_decoration_change_crashes_game()) {
return; return;
} }
@@ -470,6 +478,27 @@ void graphics_update_z_order(HWND hWnd, bool always_on_top) {
log_debug("graphics-windowed", "graphics_update_z_order returned"); log_debug("graphics-windowed", "graphics_update_z_order returned");
} }
void graphics_set_corner_preference(HWND hWnd, bool disable) {
if (!GRAPHICS_WINDOWED) {
return;
}
#if !SPICE_XP
log_debug("graphics-windowed", "graphics_set_corner_preference called");
DWM_WINDOW_CORNER_PREFERENCE corner_preference = disable ? DWMWCP_DONOTROUND : DWMWCP_DEFAULT;
DwmSetWindowAttribute(
hWnd,
DWMWA_WINDOW_CORNER_PREFERENCE,
&corner_preference, sizeof(corner_preference));
log_debug("graphics-windowed", "graphics_set_corner_preference returned");
#else
// don't link against DwmSetWindowAttribute on XP
log_debug("graphics-windowed", "graphics_set_corner_preference called; ignoring");
#endif
}
void graphics_move_resize_window(HWND hWnd) { void graphics_move_resize_window(HWND hWnd) {
if (!GRAPHICS_WINDOWED) { if (!GRAPHICS_WINDOWED) {
return; return;
@@ -503,8 +532,15 @@ bool graphics_window_decoration_change_crashes_game() {
static std::once_flag flag; static std::once_flag flag;
static bool result = false; static bool result = false;
std::call_once(flag, []() { std::call_once(flag, []() {
// ddr crashes when frame style changes // ddr and ftt crash when frame style changes
result = avs::game::is_model("MDX"); if (avs::game::is_model("MDX") || avs::game::is_model("MMD")) {
result = true;
}
// arena model hangs immediately when decoration changes
if (games::gitadora::is_arena_model()) {
result = true;
}
// changing window decoration also changes dimensions and position // changing window decoration also changes dimensions and position
// so this must be prevented // so this must be prevented
@@ -560,3 +596,56 @@ bool graphics_window_resize_breaks_game() {
return result; return result;
} }
void graphics_window_check_bounds_before_creation(int &x, int &y, const int width, const int height) {
RECT rect = {
.left = x,
.top = y,
.right = x + width,
.bottom = y + height
};
HMONITOR mon = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL);
if (mon != nullptr) {
return;
}
log_warning(
"graphics-windowed",
"window is completely out of bounds, resetting position: ({}, {}) => (0, 0)",
x, y);
x = 0;
y = 0;
rect = {
.left = x,
.top = y,
.right = x + width,
.bottom = y + height
};
mon = MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi = {};
mi.cbSize = sizeof(mi);
if (!GetMonitorInfo(mon, &mi)) {
log_warning(
"graphics-windowed",
"failed to get monitor info for out of bounds window");
return;
}
log_misc(
"graphics-windowed",
"nearest monitor for this window has work area ({}, {}),({}, {})",
mi.rcWork.left, mi.rcWork.top, mi.rcWork.right, mi.rcWork.bottom);
RECT bounds = mi.rcWork;
const bool fully_visible =
(rect.left >= bounds.left &&
rect.top >= bounds.top &&
rect.right <= bounds.right &&
rect.bottom <= bounds.bottom);
if (!fully_visible) {
log_warning("graphics-windowed", "window will not be fully visible on monitor");
}
}
+52
View File
@@ -30,6 +30,8 @@ static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr; static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr; static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr; static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr;
static decltype(GetLocaleInfoA) *GetLocaleInfoA_orig = nullptr;
static decltype(GetThreadLocale) *GetThreadLocale_orig = nullptr;
#endif #endif
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook( static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
@@ -94,6 +96,10 @@ static LCID WINAPI GetSystemDefaultLCID_hook() {
return 0x411; return 0x411;
} }
static LCID WINAPI GetThreadLocale_hook() {
return 0x411;
}
#endif #endif
static int WINAPI MultiByteToWideChar_hook( static int WINAPI MultiByteToWideChar_hook(
@@ -214,6 +220,31 @@ WideCharToMultiByte_hook(
lpUsedDefaultChar); lpUsedDefaultChar);
} }
int
WINAPI
GetLocaleInfoA_hook(
LCID Locale,
LCTYPE LCType,
LPSTR lpLCData,
int cchData) {
if (LCType == LOCALE_SISO639LANGNAME && lpLCData != NULL && cchData >= 3) {
log_misc("hooks::lang", "GetLocaleInfoA_hook hit ({:#x}, LOCALE_SISO639LANGNAME), return `ja`", Locale);
strcpy(lpLCData, "ja");
return 3;
}
if (LCType == LOCALE_SISO3166CTRYNAME && lpLCData != NULL && cchData >= 3) {
log_misc("hooks::lang",
"GetLocaleInfoA_hook hit ({:#x}, LOCALE_SISO3166CTRYNAME), return `JP`", Locale);
strcpy(lpLCData, "JP");
return 3;
}
log_misc("hooks::lang", "GetLocaleInfoA_hook hit, {:#x}, {:#x}", Locale, LCType);
return GetLocaleInfoA_orig(Locale, LCType, lpLCData, cchData);
}
#endif #endif
void hooks::lang::early_init() { void hooks::lang::early_init() {
@@ -244,6 +275,27 @@ void hooks::lang::early_init() {
} }
#ifdef SPICE64 #ifdef SPICE64
if (avs::game::is_model("UJK")) {
// CCJ build of Unity calls GetThreadLocale,
// and then GetLocaleInfoA to figure out the locale; eventually this is
// used to figure out the decimal separator, which is then used in parsing
// Double values - which fails carding in if it isn't "."
log_info("hooks::lang", "hooking GetThreadLocale");
detour::trampoline_try(
"kernel32.dll",
"GetThreadLocale",
GetThreadLocale_hook,
&GetThreadLocale_orig);
log_info("hooks::lang", "hooking GetLocaleInfoA");
detour::trampoline_try(
"kernel32.dll",
"GetLocaleInfoA",
GetLocaleInfoA_hook,
&GetLocaleInfoA_orig);
}
// for TDJ subscreen search keyboard // for TDJ subscreen search keyboard
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) { if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
log_info("hooks::lang", "hooking IsDBCSLeadByte"); log_info("hooks::lang", "hooking IsDBCSLeadByte");
+51 -20
View File
@@ -351,9 +351,9 @@ int main_implementation(int argc, char *argv[]) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true; GRAPHICS_FORCE_SINGLE_ADAPTER = true;
GRAPHICS_PREVENT_SECONDARY_WINDOW = true; GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
} }
if (options[launcher::Options::DX9DisplayAdapter].is_active() && if (options[launcher::Options::DXDisplayAdapter].is_active() &&
options[launcher::Options::DX9DisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) { options[launcher::Options::DXDisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) {
D3D9_ADAPTER = options[launcher::Options::DX9DisplayAdapter].value_uint32(); D3D9_ADAPTER = options[launcher::Options::DXDisplayAdapter].value_uint32();
// when we fix up adapter numbers, we only fix the first adapter, and not any subsequent // when we fix up adapter numbers, we only fix the first adapter, and not any subsequent
// adapters, so we can't deal with multi-monitor games // adapters, so we can't deal with multi-monitor games
@@ -473,6 +473,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::spice2x_SDVXSubRedraw].value_bool()) { if (options[launcher::Options::spice2x_SDVXSubRedraw].value_bool()) {
SUBSCREEN_FORCE_REDRAW = true; SUBSCREEN_FORCE_REDRAW = true;
} }
if (options[launcher::Options::SDVXSubMonitorOverride].is_active()) {
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::SDVXSubMonitorOverride].value_text();
}
if (options[launcher::Options::LoadIIDXModule].value_bool()) { if (options[launcher::Options::LoadIIDXModule].value_bool()) {
attach_iidx = true; attach_iidx = true;
} }
@@ -587,6 +590,16 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::PopnMusicForceSDMode].value_bool()) { if (options[launcher::Options::PopnMusicForceSDMode].value_bool()) {
avs::ea3::PCB_TYPE = 0; avs::ea3::PCB_TYPE = 0;
} }
if (options[launcher::Options::PopnNoSub].value_bool()) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
}
if (options[launcher::Options::PopnSubMonitorOverride].is_active()) {
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::PopnSubMonitorOverride].value_text();
}
if (options[launcher::Options::PopnNativeTouch].value_bool()) {
games::popn::NATIVE_TOUCH = true;
}
if (options[launcher::Options::LoadMetalGearArcadeModule].value_bool()) { if (options[launcher::Options::LoadMetalGearArcadeModule].value_bool()) {
attach_mga = true; attach_mga = true;
} }
@@ -1107,6 +1120,7 @@ int main_implementation(int argc, char *argv[]) {
} }
GRAPHICS_WINDOW_ALWAYS_ON_TOP = options[launcher::Options::spice2x_WindowAlwaysOnTop].value_bool(); GRAPHICS_WINDOW_ALWAYS_ON_TOP = options[launcher::Options::spice2x_WindowAlwaysOnTop].value_bool();
GRAPHICS_WINDOW_BACKBUFFER_SCALE = options[launcher::Options::WindowForceScaling].value_bool(); GRAPHICS_WINDOW_BACKBUFFER_SCALE = options[launcher::Options::WindowForceScaling].value_bool();
GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS = options[launcher::Options::WindowDisableRoundedCorners].value_bool();
// IIDX/SDVX Windowed Subscreen // IIDX/SDVX Windowed Subscreen
if (options[launcher::Options::spice2x_IIDXWindowedSubscreenSize].is_active()) { if (options[launcher::Options::spice2x_IIDXWindowedSubscreenSize].is_active()) {
@@ -1127,6 +1141,9 @@ int main_implementation(int argc, char *argv[]) {
options[launcher::Options::SDVXWindowedSubscreenAlwaysOnTop].value_bool()) { options[launcher::Options::SDVXWindowedSubscreenAlwaysOnTop].value_bool()) {
GRAPHICS_WSUB_ALWAYS_ON_TOP = true; GRAPHICS_WSUB_ALWAYS_ON_TOP = true;
} }
if (options[launcher::Options::IIDXSubMonitorOverride].is_active()) {
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::IIDXSubMonitorOverride].value_text();
}
if (options[launcher::Options::spice2x_JubeatLegacyTouch].value_bool()) { if (options[launcher::Options::spice2x_JubeatLegacyTouch].value_bool()) {
games::jb::TOUCH_LEGACY_BOX = true; games::jb::TOUCH_LEGACY_BOX = true;
@@ -1227,7 +1244,7 @@ int main_implementation(int argc, char *argv[]) {
acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::THREAD_MODE; acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::THREAD_MODE;
} else if (options[launcher::Options::DDRP4IOBufferMode].value_text() == "backfill") { } else if (options[launcher::Options::DDRP4IOBufferMode].value_text() == "backfill") {
acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::BACKFILL_MODE; acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::BACKFILL_MODE;
} }
} }
if (options[launcher::Options::MidiAlgoVer].is_active()) { if (options[launcher::Options::MidiAlgoVer].is_active()) {
@@ -1294,7 +1311,19 @@ int main_implementation(int argc, char *argv[]) {
} }
// log // log
#ifndef SPICE_LINUX #if SPICE_LINUX
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for Linux");
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for Linux");
#endif
#elif SPICE_XP
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for WinXP");
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for WinXP");
#endif
#else
#ifdef SPICE64 #ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x)"); log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x)");
#elif SPICE32_LARGE_ADDRESS_AWARE #elif SPICE32_LARGE_ADDRESS_AWARE
@@ -1302,12 +1331,6 @@ int main_implementation(int argc, char *argv[]) {
#else #else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x)"); log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x)");
#endif #endif
#else
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for Linux");
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for Linux");
#endif
#endif #endif
log_info("launcher", "{}", VERSION_STRING); log_info("launcher", "{}", VERSION_STRING);
@@ -1364,7 +1387,7 @@ int main_implementation(int argc, char *argv[]) {
log_warning( log_warning(
"launcher", "launcher",
"multiple values for -{}, command line args take precedence: {}", "multiple values for -{}, command line args take precedence: {}",
option.get_definition().name, option.get_definition().name,
value); value);
} else { } else {
log_warning( log_warning(
@@ -1379,7 +1402,7 @@ int main_implementation(int argc, char *argv[]) {
if (launcher::USE_CMD_OVERRIDE) { if (launcher::USE_CMD_OVERRIDE) {
log_info( log_info(
"launcher", "launcher",
"user specified -cmdoverride, therefore command line args took precedence over spicecfg"); "user specified -cmdoverride, therefore command line args took precedence over spicecfg");
} else { } else {
log_warning( log_warning(
"launcher", "launcher",
@@ -1745,11 +1768,21 @@ int main_implementation(int argc, char *argv[]) {
break; break;
} }
// HELLO! Pop'n Music
if (check_dll("popn.dll")) { if (check_dll("popn.dll")) {
avs::game::DLL_NAME = "popn.dll"; avs::game::DLL_NAME = "popn.dll";
attach_io = true; attach_io = true;
attach_hpm = true;
if (check_dll("libaio-iob2_video.dll")) {
// pop'n music (pika cabinet)
attach_popn = true;
// automatically show cursor in windowed mode to interact with second window (sub)
if (GRAPHICS_WINDOWED) {
GRAPHICS_SHOW_CURSOR = true;
}
} else {
// HELLO! Pop'n Music
attach_hpm = true;
}
break; break;
} }
@@ -1802,9 +1835,7 @@ int main_implementation(int argc, char *argv[]) {
attach_ftt = true; attach_ftt = true;
// the game is windowed by default unless we set the env // the game is windowed by default unless we set the env
if (GRAPHICS_WINDOWED) { if (!GRAPHICS_WINDOWED) {
GRAPHICS_WINDOWED = false;
} else {
SetEnvironmentVariable("DAMAC_VIEWER_FULLSCREEN", "0"); SetEnvironmentVariable("DAMAC_VIEWER_FULLSCREEN", "0");
} }
@@ -2226,7 +2257,7 @@ int main_implementation(int argc, char *argv[]) {
game->attach(); game->attach();
} }
#ifdef SPICE64 #if SPICE64 && !SPICE_XP
if (!cfg::CONFIGURATOR_STANDALONE) { if (!cfg::CONFIGURATOR_STANDALONE) {
if (games::iidx::TDJ_CAMERA) { if (games::iidx::TDJ_CAMERA) {
games::iidx::init_camera_hooks(); games::iidx::init_camera_hooks();
@@ -2507,7 +2538,7 @@ int main_implementation(int argc, char *argv[]) {
// disable poke // disable poke
games::iidx::poke::disable(); games::iidx::poke::disable();
#ifdef SPICE64 #if SPICE64 && !SPICE_XP
games::iidx::camera_release(); games::iidx::camera_release();
#endif #endif
+93 -30
View File
@@ -32,6 +32,7 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
static const std::vector<std::string> CATEGORY_ORDER_ADVANCED = { static const std::vector<std::string> CATEGORY_ORDER_ADVANCED = {
"Game Options (Advanced)", "Game Options (Advanced)",
"Game Options (Peripherals)",
"Overlay", "Overlay",
"Network (Advanced)", "Network (Advanced)",
"Performance", "Performance",
@@ -218,11 +219,11 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.picker = OptionPickerType::Monitor, .picker = OptionPickerType::Monitor,
}, },
{ {
// DX9DisplayAdapter // DXDisplayAdapter
.title = "DX9 Primary Display Adapter Override", .title = "DX Primary Display Adapter Override",
.name = "monitor", .name = "monitor",
.display_name = "dx9mainadapter", .display_name = "dxmainadapter",
.aliases = "dx9mainadapter", .aliases = "dxmainadapter",
.desc = "Prefer to use Change Main Monitor option instead of this one.\n\n" .desc = "Prefer to use Change Main Monitor option instead of this one.\n\n"
"Sets the display that the game will be opened in, for multiple monitors.\n\n" "Sets the display that the game will be opened in, for multiple monitors.\n\n"
"0 is the primary monitor, 1 is the second monitor, and so on.\n\n" "0 is the primary monitor, 1 is the second monitor, and so on.\n\n"
@@ -513,7 +514,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Flip the camera order.", .desc = "Flip the camera order.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXDisableCameras // IIDXDisableCameras
@@ -523,7 +524,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool, .type = OptionType::Bool,
.hidden = true, .hidden = true,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXCabCamAccess // IIDXCabCamAccess
@@ -533,7 +534,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"Only turn this on if you have OFFICIAL arcade cameras connected to the correct USB ports. Default: auto.", "Only turn this on if you have OFFICIAL arcade cameras connected to the correct USB ports. Default: auto.",
.type = OptionType::Enum, .type = OptionType::Enum,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
.elements = { .elements = {
{"auto", ""}, {"auto", ""},
{"off", ""}, {"off", ""},
@@ -549,7 +550,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Experimental camera support for IIDX 27+ via texture hooking. Requires camera(s) that support YUY2 or NV12 encoding.", .desc = "Experimental camera support for IIDX 27+ via texture hooking. Requires camera(s) that support YUY2 or NV12 encoding.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXCamHookRatio // IIDXCamHookRatio
@@ -561,7 +562,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Enum, .type = OptionType::Enum,
.hidden = true, .hidden = true,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
.elements = { .elements = {
{"43", "4:3"}, {"43", "4:3"},
{"169", "16:9"}, {"169", "16:9"},
@@ -579,7 +580,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "0x5817a0,0x6fffbd8,0xbbae40,0xe0,0x30", .setting_name = "0x5817a0,0x6fffbd8,0xbbae40,0xe0,0x30",
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXCamHookTopId // IIDXCamHookTopId
@@ -594,7 +595,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "vid_1234&pid_5678", .setting_name = "vid_1234&pid_5678",
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// IIDXCamHookFrontId // IIDXCamHookFrontId
@@ -609,7 +610,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "vid_90ab&pid_cdef", .setting_name = "vid_90ab&pid_cdef",
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "IIDX Sound Output Device", .title = "IIDX Sound Output Device",
@@ -772,11 +773,11 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options (Advanced)", .category = "Game Options (Advanced)",
}, },
{ {
.title = "SDVX Force 720p", .title = "SDVX Vivid Wave Force 720p Window (DEPRECATED - use -windowsize instead)",
.name = "sdvx720", .name = "sdvx720",
.desc = "Force Sound Voltex 720p display mode, used by older games. " .desc = "Old & deprecated option for launching Vivid Wave in 720p when using windowed mode.",
"For newer games, use window resize function instead.",
.type = OptionType::Bool, .type = OptionType::Bool,
.hidden = true,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Advanced)",
}, },
@@ -786,7 +787,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Enable Sound Voltex printer emulation.", .desc = "Enable Sound Voltex printer emulation.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// SDVXPrinterOutputPath // SDVXPrinterOutputPath
@@ -795,7 +796,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Path to folder where images will be stored.", .desc = "Path to folder where images will be stored.",
.type = OptionType::Text, .type = OptionType::Text,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
.picker = OptionPickerType::DirectoryPath, .picker = OptionPickerType::DirectoryPath,
}, },
{ {
@@ -804,7 +805,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Clean up saved images in the output directory on startup.", .desc = "Clean up saved images in the output directory on startup.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "SDVX Printer Output Overwrite", .title = "SDVX Printer Output Overwrite",
@@ -812,7 +813,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Always overwrite the same file in output directory.", .desc = "Always overwrite the same file in output directory.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// SDVXPrinterOutputFormat // SDVXPrinterOutputFormat
@@ -822,7 +823,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "(png/bmp/tga/jpg)", .setting_name = "(png/bmp/tga/jpg)",
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "SDVX Printer JPG Quality", .title = "SDVX Printer JPG Quality",
@@ -831,7 +832,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Integer, .type = OptionType::Integer,
.setting_name = "(0-100)", .setting_name = "(0-100)",
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "SDVX Disable Cameras (DEPRECATED - no longer needed)", .title = "SDVX Disable Cameras (DEPRECATED - no longer needed)",
@@ -842,7 +843,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool, .type = OptionType::Bool,
.hidden = true, .hidden = true,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Game Options", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "SDVX FS Subscreen Native Touch Handling", .title = "SDVX FS Subscreen Native Touch Handling",
@@ -928,6 +929,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
{"bottomright", "for landscape"}, {"bottomright", "for landscape"},
}, },
}, },
{
// SDVXSubMonitorOverride
.title = "SDVX Subscreen Monitor Override",
.name = "sdvxsubmonitor",
.desc = "If you have three or more monitors, this option can be set to tell the game which monitor is the subscreen.",
.type = OptionType::Text,
.setting_name = "\\\\.\\DISPLAY3",
.game_name = "Sound Voltex",
.category = "Monitor",
.picker = OptionPickerType::Monitor,
},
{ {
.title = "Force Load DDR Module", .title = "Force Load DDR Module",
.name = "ddr", .name = "ddr",
@@ -977,6 +989,38 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Pop'n Music", .game_name = "Pop'n Music",
.category = "Game Options", .category = "Game Options",
}, },
{
// PopnNoSub
.title = "Pop'n Music PikaPika Subscreen Disable",
.name = "popnnosub",
.desc = "Prevents PikaPika model subscreen from launching a separate window.",
.type = OptionType::Bool,
.game_name = "Pop'n Music",
.category = "Game Options",
},
{
// PopnSubMonitorOverride
.title = "Pop'n Music PikaPika Subscreen Monitor Override",
.name = "popnsubmonitor",
.desc = "If you have three or more monitors, this option can be set to tell the game which monitor is the subscreen.",
.type = OptionType::Text,
.setting_name = "\\\\.\\DISPLAY3",
.game_name = "Pop'n Music",
.category = "Monitor",
.picker = OptionPickerType::Monitor,
},
{
// PopnNativeTouch
.title = "Pop'n Music PikaPika Native Touch Handling",
.name = "popnnativetouch",
.desc = "Disables touch hooks and lets the game access a touch screen directly. "
"Requires a touch screen to be connected as a secondary monitor. "
"Touch input must be routed to the primary screen via Windows Tablet PC settings. "
"Enable this when you get duplicate touch inputs from an actual touch screen.",
.type = OptionType::Bool,
.game_name = "Pop'n Music",
.category = "Game Options (Advanced)",
},
{ {
.title = "Force Load HELLO! Pop'n Music Module", .title = "Force Load HELLO! Pop'n Music Module",
.name = "hpm", .name = "hpm",
@@ -2187,6 +2231,14 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool, .type = OptionType::Bool,
.category = "Graphics (Windowed)", .category = "Graphics (Windowed)",
}, },
{
// WindowDisableRoundedCorners
.title = "Disable Round Window Corners",
.name = "windownoroundcorners",
.desc = "Windows 11 and above only: Disables rounded corners on the game window(s).",
.type = OptionType::Bool,
.category = "Graphics (Windowed)",
},
{ {
// spice2x_IIDXWindowedSubscreenSize // spice2x_IIDXWindowedSubscreenSize
.title = "IIDX Windowed Subscreen Size", .title = "IIDX Windowed Subscreen Size",
@@ -2316,7 +2368,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"Note: this is NOT for the motion camera - you still need a real RealSense camera for that!", "Note: this is NOT for the motion camera - you still need a real RealSense camera for that!",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "DANCERUSH", .game_name = "DANCERUSH",
.category = "Game Options", .category = "Game Options (Peripherals)",
}, },
{ {
// spice2x_IIDXNativeTouch // spice2x_IIDXNativeTouch
@@ -2343,6 +2395,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options", .category = "Game Options",
}, },
{
// IIDXSubMonitorOverride
.title = "IIDX TDJ Subscreen Monitor Override",
.name = "iidxsubmonitor",
.desc = "If you have three or more monitors, this option can be set to tell the game which monitor is the subscreen.",
.type = OptionType::Text,
.setting_name = "\\\\.\\DISPLAY3",
.game_name = "Beatmania IIDX",
.category = "Monitor",
.picker = OptionPickerType::Monitor,
},
{ {
// spice2x_IIDXEmulateSubscreenKeypadTouch // spice2x_IIDXEmulateSubscreenKeypadTouch
.title = "IIDX TDJ Subscreen Keypad Touch Emulation", .title = "IIDX TDJ Subscreen Keypad Touch Emulation",
@@ -2639,7 +2702,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Allow game to access camera; camera must be compatible with game.", .desc = "Allow game to access camera; camera must be compatible with game.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options", .category = "Game Options (Peripherals)",
}, },
{ {
// LovePlusPrinterOutputPath // LovePlusPrinterOutputPath
@@ -2648,7 +2711,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Path to folder where images will be stored.", .desc = "Path to folder where images will be stored.",
.type = OptionType::Text, .type = OptionType::Text,
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
.picker = OptionPickerType::DirectoryPath, .picker = OptionPickerType::DirectoryPath,
}, },
{ {
@@ -2657,7 +2720,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Clean up saved images in the output directory on startup.", .desc = "Clean up saved images in the output directory on startup.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "LovePlus Printer Output Overwrite", .title = "LovePlus Printer Output Overwrite",
@@ -2665,7 +2728,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.desc = "Always overwrite the same file in output directory.", .desc = "Always overwrite the same file in output directory.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// LovePlusPrinterOutputFormat // LovePlusPrinterOutputFormat
@@ -2675,7 +2738,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "(png/bmp/tga/jpg)", .setting_name = "(png/bmp/tga/jpg)",
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
.title = "LovePlus Printer JPG Quality", .title = "LovePlus Printer JPG Quality",
@@ -2684,7 +2747,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Integer, .type = OptionType::Integer,
.setting_name = "(0-100)", .setting_name = "(0-100)",
.game_name = "LovePlus", .game_name = "LovePlus",
.category = "Game Options (Advanced)", .category = "Game Options (Peripherals)",
}, },
{ {
// OptionConflictResolution // OptionConflictResolution
@@ -2705,7 +2768,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
"option to bypass camera error during boot, allowing you to try out the game.", "option to bypass camera error during boot, allowing you to try out the game.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Otoca D'or", .game_name = "Otoca D'or",
.category = "Game Options", .category = "Game Options (Peripherals)",
} }
}; };
+7 -1
View File
@@ -27,7 +27,7 @@ namespace launcher {
CaptureCursor, CaptureCursor,
ShowCursor, ShowCursor,
PrimaryMonitor, PrimaryMonitor,
DX9DisplayAdapter, DXDisplayAdapter,
GraphicsForceSingleAdapter, GraphicsForceSingleAdapter,
GraphicsForceRefresh, GraphicsForceRefresh,
FullscreenResolution, FullscreenResolution,
@@ -92,12 +92,16 @@ namespace launcher {
SDVXDigitalKnobSocd, SDVXDigitalKnobSocd,
spice2x_SDVXAsioDriver, spice2x_SDVXAsioDriver,
spice2x_SDVXSubPos, spice2x_SDVXSubPos,
SDVXSubMonitorOverride,
LoadDDRModule, LoadDDRModule,
DDR43Mode, DDR43Mode,
DDRSkipCodecRegisteration, DDRSkipCodecRegisteration,
LoadPopnMusicModule, LoadPopnMusicModule,
PopnMusicForceHDMode, PopnMusicForceHDMode,
PopnMusicForceSDMode, PopnMusicForceSDMode,
PopnNoSub,
PopnSubMonitorOverride,
PopnNativeTouch,
LoadHelloPopnMusicModule, LoadHelloPopnMusicModule,
LoadGitaDoraModule, LoadGitaDoraModule,
GitaDoraTwoChannelAudio, GitaDoraTwoChannelAudio,
@@ -229,6 +233,7 @@ namespace launcher {
spice2x_WindowPosition, spice2x_WindowPosition,
spice2x_WindowAlwaysOnTop, spice2x_WindowAlwaysOnTop,
WindowForceScaling, WindowForceScaling,
WindowDisableRoundedCorners,
spice2x_IIDXWindowedSubscreenSize, spice2x_IIDXWindowedSubscreenSize,
spice2x_IIDXWindowedSubscreenPosition, spice2x_IIDXWindowedSubscreenPosition,
IIDXWindowedSubscreenBorderless, IIDXWindowedSubscreenBorderless,
@@ -243,6 +248,7 @@ namespace launcher {
DRSRGBCameraHook, DRSRGBCameraHook,
spice2x_IIDXNativeTouch, spice2x_IIDXNativeTouch,
spice2x_IIDXNoSub, spice2x_IIDXNoSub,
IIDXSubMonitorOverride,
spice2x_IIDXEmulateSubscreenKeypadTouch, spice2x_IIDXEmulateSubscreenKeypadTouch,
spice2x_AutoCard, spice2x_AutoCard,
spice2x_LowLatencySharedAudio, spice2x_LowLatencySharedAudio,
+3 -4
View File
@@ -86,13 +86,12 @@ namespace launcher {
if (LAUNCHER_ARGC > 0) { if (LAUNCHER_ARGC > 0) {
// build cmd line // build cmd line
std::stringstream cmd_line; std::string cmd_line = "START \"\" ";
cmd_line << "START \"\" ";
for (int i = 0; i < LAUNCHER_ARGC; i++) for (int i = 0; i < LAUNCHER_ARGC; i++)
cmd_line << " \"" << LAUNCHER_ARGV[i] << "\""; cmd_line += " \"" + std::string(LAUNCHER_ARGV[i]) + "\"";
// run command // run command
system(cmd_line.str().c_str()); system(cmd_line.c_str());
} }
} }
+5
View File
@@ -12,6 +12,7 @@
#include "cfg/screen_resize.h" #include "cfg/screen_resize.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "overlay/overlay.h" #include "overlay/overlay.h"
#include "overlay/windows/generic_sub.h" #include "overlay/windows/generic_sub.h"
@@ -405,6 +406,10 @@ namespace wintouchemu {
// use mouse position as ImGui overlay will block the touch window // use mouse position as ImGui overlay will block the touch window
log_info("wintouchemu", "use mouse cursor API for ldj overlay subscreen"); log_info("wintouchemu", "use mouse cursor API for ldj overlay subscreen");
USE_MOUSE = true; USE_MOUSE = true;
} else if (games::popn::is_pikapika_model()) {
// same as iidx case above
log_info("wintouchemu", "use mouse cursor API for popn overlay subscreen");
USE_MOUSE = true;
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) { } else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
log_info("wintouchemu", "use mouse cursor API for gitadora overlay subscreen"); log_info("wintouchemu", "use mouse cursor API for gitadora overlay subscreen");
USE_MOUSE = true; USE_MOUSE = true;
+2
View File
@@ -169,8 +169,10 @@ namespace ImGui {
ImGui::PushStyleColor(ImGuiCol_Header, 0); ImGui::PushStyleColor(ImGuiCol_Header, 0);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, 0); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, 0);
ImGui::PushStyleColor(ImGuiCol_HeaderActive, 0); ImGui::PushStyleColor(ImGuiCol_HeaderActive, 0);
ImGui::PushTabStop(false); // prevent tab navigation
ImGui::Selectable("##row", false, ImGui::Selectable("##row", false,
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap); ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
ImGui::PopTabStop();
ImGui::PopStyleColor(3); ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(200, 200, 200, 24)); ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(200, 200, 200, 24));
+10 -8
View File
@@ -265,7 +265,8 @@ static void ImGui_ImplSpice_UpdateMousePos() {
// trigger any overlay, don't process anything else // trigger any overlay, don't process anything else
const auto is_windowed_subscreen = const auto is_windowed_subscreen =
(GRAPHICS_IIDX_WSUB && active_window == TDJ_SUBSCREEN_WINDOW) || (GRAPHICS_IIDX_WSUB && active_window == TDJ_SUBSCREEN_WINDOW) ||
(active_window == SDVX_SUBSCREEN_WINDOW); (active_window == SDVX_SUBSCREEN_WINDOW) ||
(active_window == POPN_SUBSCREEN_WINDOW);
if (is_windowed_subscreen) { if (is_windowed_subscreen) {
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
return; return;
@@ -348,6 +349,10 @@ void ImGui_ImplSpice_NewFrame() {
io.DeltaTime = (float) (current_time - g_Time) / g_TicksPerSecond; io.DeltaTime = (float) (current_time - g_Time) / g_TicksPerSecond;
g_Time = current_time; g_Time = current_time;
// only process new input from devices if window is in focus
// (when not in focus, all mouse/keyboard events are treated as released)
const auto accept_new_input = superexit::has_focus() && !rawinput::OS_WINDOW_ACTIVE;
// remember old state // remember old state
std::array<BYTE, VKEY_MAX> KeysDownOld; std::array<BYTE, VKEY_MAX> KeysDownOld;
for (size_t i = 0; i < VKEY_MAX; i++) { for (size_t i = 0; i < VKEY_MAX; i++) {
@@ -360,11 +365,6 @@ void ImGui_ImplSpice_NewFrame() {
g_MouseDown.fill(false); g_MouseDown.fill(false);
g_KeysDown.fill(false); g_KeysDown.fill(false);
// early quit if window not in focus
if (!superexit::has_focus() || rawinput::OS_WINDOW_ACTIVE) {
return;
}
// apply windows mouse buttons // apply windows mouse buttons
g_MouseDown[ImGuiMouseButton_Left] |= (get_async_primary_mouse()) != 0; g_MouseDown[ImGuiMouseButton_Left] |= (get_async_primary_mouse()) != 0;
g_MouseDown[ImGuiMouseButton_Right] |= (get_async_secondary_mouse()) != 0; g_MouseDown[ImGuiMouseButton_Right] |= (get_async_secondary_mouse()) != 0;
@@ -373,7 +373,7 @@ void ImGui_ImplSpice_NewFrame() {
// read new keys state // read new keys state
static long mouse_wheel_last = 0; static long mouse_wheel_last = 0;
long mouse_wheel = 0; long mouse_wheel = 0;
if (RI_MGR != nullptr) { if (RI_MGR != nullptr && accept_new_input) {
auto devices = RI_MGR->devices_get(); auto devices = RI_MGR->devices_get();
for (auto &device : devices) { for (auto &device : devices) {
switch (device.type) { switch (device.type) {
@@ -470,7 +470,9 @@ void ImGui_ImplSpice_NewFrame() {
// update OS mouse position // update OS mouse position
const auto old_mouse_pos = io.MousePos; const auto old_mouse_pos = io.MousePos;
ImGui_ImplSpice_UpdateMousePos(); if (accept_new_input) {
ImGui_ImplSpice_UpdateMousePos();
}
const auto new_mouse_pos = io.MousePos; const auto new_mouse_pos = io.MousePos;
// update mouse buttons // update mouse buttons
+7 -3
View File
@@ -5,6 +5,7 @@
#include "games/io.h" #include "games/io.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "touch/touch.h" #include "touch/touch.h"
@@ -35,6 +36,7 @@
#include "windows/iopanel_ddr.h" #include "windows/iopanel_ddr.h"
#include "windows/iopanel_gfdm.h" #include "windows/iopanel_gfdm.h"
#include "windows/iopanel_iidx.h" #include "windows/iopanel_iidx.h"
#include "windows/popn_sub.h"
#include "windows/sdvx_sub.h" #include "windows/sdvx_sub.h"
#include "windows/keypad.h" #include "windows/keypad.h"
#include "windows/log.h" #include "windows/log.h"
@@ -69,7 +71,7 @@ namespace overlay {
} }
return ImVec2(apply_scaling(x), apply_scaling(y)); return ImVec2(apply_scaling(x), apply_scaling(y));
} }
ImVec2 apply_scaling_to_vector(const ImVec2& input) { ImVec2 apply_scaling_to_vector(const ImVec2& input) {
return apply_scaling_to_vector(input.x, input.y); return apply_scaling_to_vector(input.x, input.y);
} }
@@ -350,7 +352,7 @@ void overlay::SpiceOverlay::init() {
this->window_add(window_config = new overlay::windows::Config(this)); this->window_add(window_config = new overlay::windows::Config(this));
this->window_add(window_control = new overlay::windows::Control(this)); this->window_add(window_control = new overlay::windows::Control(this));
this->window_add(window_log = new overlay::windows::Log(this)); this->window_add(window_log = new overlay::windows::Log(this));
#ifdef SPICE64 #if SPICE64 && !SPICE_XP
if (avs::game::is_model("LDJ")) { if (avs::game::is_model("LDJ")) {
this->window_add(window_camera = new overlay::windows::CameraControl(this)); this->window_add(window_camera = new overlay::windows::CameraControl(this));
} }
@@ -415,6 +417,8 @@ void overlay::SpiceOverlay::init() {
window_sub = new overlay::windows::SDVXSubScreen(this); window_sub = new overlay::windows::SDVXSubScreen(this);
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) { } else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
window_sub = new overlay::windows::GitaDoraSubScreen(this); window_sub = new overlay::windows::GitaDoraSubScreen(this);
} else if (games::popn::is_pikapika_model()) {
window_sub = new overlay::windows::PopnSubScreen(this);
} }
if (window_sub) { if (window_sub) {
@@ -738,4 +742,4 @@ void overlay::SpiceOverlay::add_font(const char* font, ImFontConfig* config, con
} else { } else {
log_misc("overlay", "font not found: {}", full_path.string()); log_misc("overlay", "font not found: {}", full_path.string());
} }
} }
@@ -1,6 +1,6 @@
#include "camera_control.h" #include "camera_control.h"
#if SPICE64 #if SPICE64 && !SPICE_XP
#include <games/io.h> #include <games/io.h>
#include <strmif.h> #include <strmif.h>
@@ -193,7 +193,7 @@ namespace overlay::windows {
} }
ImGui::Separator(); ImGui::Separator();
// reset button // reset button
if (ImGui::Button("Reset")) { if (ImGui::Button("Reset")) {
selectedCamera->ResetCameraControlProps(); selectedCamera->ResetCameraControlProps();
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#if SPICE64 #if SPICE64 && !SPICE_XP
#include "overlay/window.h" #include "overlay/window.h"
#include <strmif.h> #include <strmif.h>
+7 -7
View File
@@ -144,7 +144,7 @@ namespace overlay::windows {
" xxxx xxxx xxxx xxxx "); " xxxx xxxx xxxx xxxx ");
ImGui::EndDisabled(); ImGui::EndDisabled();
} }
ImGui::PopID(); ImGui::PopID();
return clicked; return clicked;
} }
@@ -152,8 +152,8 @@ namespace overlay::windows {
void CardManager::open_card_editor() { void CardManager::open_card_editor() {
if (this->current_card) { if (this->current_card) {
const auto card = this->current_card; const auto card = this->current_card;
strcpy_s(this->name_buffer, std::size(this->name_buffer), card->name.c_str()); snprintf(this->name_buffer, std::size(this->name_buffer), "%s", card->name.c_str());
strcpy_s(this->card_buffer, std::size(this->card_buffer), card->id.c_str()); snprintf(this->card_buffer, std::size(this->card_buffer), "%s", card->id.c_str());
this->color_buffer[0] = card->color[0]; this->color_buffer[0] = card->color[0];
this->color_buffer[1] = card->color[1]; this->color_buffer[1] = card->color[1];
this->color_buffer[2] = card->color[2]; this->color_buffer[2] = card->color[2];
@@ -227,7 +227,7 @@ namespace overlay::windows {
}; };
generate_search_string(&card); generate_search_string(&card);
this->cards.emplace_back(card); this->cards.emplace_back(card);
// mark this card as the selected one // mark this card as the selected one
this->current_card = &this->cards.back(); this->current_card = &this->cards.back();
} }
@@ -301,7 +301,7 @@ namespace overlay::windows {
// search for card // search for card
// //
// setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below // setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below
// to return true, making it necessary to provide a callback... // to return true, making it necessary to provide a callback...
ImGui::SetNextItemWidth(overlay::apply_scaling(240)); ImGui::SetNextItemWidth(overlay::apply_scaling(240));
if (ImGui::InputTextWithHint("", "Type here to search..", &this->search_filter)) { if (ImGui::InputTextWithHint("", "Type here to search..", &this->search_filter)) {
@@ -399,11 +399,11 @@ namespace overlay::windows {
// cards from card manager JSON // cards from card manager JSON
for (auto &card : this->cards) { for (auto &card : this->cards) {
if (!this->search_filter_in_lower_case.empty() && !card.search_string.empty()) { if (!this->search_filter_in_lower_case.empty() && !card.search_string.empty()) {
const bool matched = const bool matched =
card.search_string.find(this->search_filter_in_lower_case) != std::string::npos; card.search_string.find(this->search_filter_in_lower_case) != std::string::npos;
if (!matched) { if (!matched) {
continue; continue;
} }
+176 -79
View File
@@ -17,6 +17,7 @@
#include "external/imgui/misc/cpp/imgui_stdlib.h" #include "external/imgui/misc/cpp/imgui_stdlib.h"
#include "games/io.h" #include "games/io.h"
#include "games/sdvx/sdvx.h" #include "games/sdvx/sdvx.h"
#include "games/popn/popn.h"
#include "avs/core.h" #include "avs/core.h"
#include "avs/ea3.h" #include "avs/ea3.h"
#include "avs/game.h" #include "avs/game.h"
@@ -61,7 +62,7 @@ namespace overlay::windows {
constexpr ImVec4 TEXT_COLOR_GREEN(0.f, 1.f, 0.f, 1.f); constexpr ImVec4 TEXT_COLOR_GREEN(0.f, 1.f, 0.f, 1.f);
constexpr ImVec4 TEXT_COLOR_RED(1.f, 0.f, 0.f, 1.f); constexpr ImVec4 TEXT_COLOR_RED(1.f, 0.f, 0.f, 1.f);
constexpr uint32_t OPTION_INPUT_TEXT_WIDTH = 512; constexpr uint32_t OPTION_INPUT_TEXT_WIDTH = 512;
std::unique_ptr<AsioDriverList> asio_driver_list; std::unique_ptr<AsioDriverList> asio_driver_list;
Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) { Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) {
@@ -114,13 +115,23 @@ namespace overlay::windows {
if (cfg::CONFIGURATOR_STANDALONE) { if (cfg::CONFIGURATOR_STANDALONE) {
const auto file_hints = games::get_game_file_hints(game_name); const auto file_hints = games::get_game_file_hints(game_name);
if (file_hints) { if (file_hints) {
for (auto &file_hint : *file_hints) { for (auto &file_hint_list : *file_hints) {
if (fileutils::file_exists(file_hint) || bool matches_all_files = true;
fileutils::dir_exists(file_hint) || for (auto &file_hint : file_hint_list) {
fileutils::file_exists(std::filesystem::path("modules") / file_hint) || const auto matched =
fileutils::file_exists(std::filesystem::path("contents") / file_hint) || (fileutils::file_exists(file_hint) ||
fileutils::file_exists(MODULE_PATH / file_hint)) fileutils::dir_exists(file_hint) ||
{ fileutils::file_exists(std::filesystem::path("modules") / file_hint) ||
fileutils::file_exists(std::filesystem::path("contents") / file_hint) ||
fileutils::file_exists(MODULE_PATH / file_hint));
if (!matched) {
matches_all_files = false;
break;
}
}
if (matches_all_files) {
this->games_selected = games_list.size() - 1; this->games_selected = games_list.size() - 1;
this->games_selected_name = game_name; this->games_selected_name = game_name;
eamuse_set_game(game_name); eamuse_set_game(game_name);
@@ -273,18 +284,8 @@ namespace overlay::windows {
// keypad buttons // keypad buttons
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
if (this->games_selected_name == "Beatmania IIDX") { this->build_keypad_warning();
ImGui::Indent(INDENT);
ImGui::TextColored(
ImVec4(1, 0.5f, 0.5f, 1.f),
"WARNING: Lightning Model (TDJ) I/O will ignore the keypad!");
ImGui::TextWrapped(
"Use Toggle Sub Screen button to show the overlay and use your mouse, "
"connect using SpiceCompanion app, or connect a touch screen to enter "
"the PIN.");
ImGui::Unindent(INDENT);
ImGui::TextUnformatted("");
}
auto keypad_buttons = games::get_buttons_keypads(this->games_selected_name); auto keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
auto keypad_count = eamuse_get_game_keypads_name(); auto keypad_count = eamuse_get_game_keypads_name();
if (keypad_count == 1) { if (keypad_count == 1) {
@@ -515,7 +516,7 @@ namespace overlay::windows {
ImGui::BeginChild("SearchOptions", ImVec2( ImGui::BeginChild("SearchOptions", ImVec2(
0, ImGui::GetWindowContentRegionMax().y - page_offset), false); 0, ImGui::GetWindowContentRegionMax().y - page_offset), false);
// search from all options // search from all options
ImGui::Spacing(); ImGui::Spacing();
ImGui::SetNextItemWidth(420.f); ImGui::SetNextItemWidth(420.f);
@@ -592,6 +593,34 @@ namespace overlay::windows {
} }
} }
void Config::build_keypad_warning() {
// not checking the -iidxtdj option here, we could do that in the future
const bool is_tdj = this->games_selected_name == "Beatmania IIDX";
// not using games::popn::is_pikapika_model() here since that always returns false on 32-bit
const bool is_popn = this->games_selected_name == "Pop'n Music" && avs::game::SPEC[0] == 'D';
if (!is_tdj && !is_popn) {
return;
}
ImGui::Indent(INDENT);
if (is_tdj) {
ImGui::TextColored(
ImVec4(1, 0.5f, 0.5f, 1.f),
"WARNING: Lightning Model (TDJ) I/O will ignore keypad number input!");
} else if (is_popn) {
ImGui::TextColored(
ImVec4(1, 0.5f, 0.5f, 1.f),
"WARNING: PikaPika Pop-Kun model will ignore keypad number input!");
}
ImGui::TextWrapped(
"Use Toggle Sub Screen button (Overlay tab) to show the overlay and use your mouse, "
"connect using SpiceCompanion app, or connect a touch screen to enter "
"the PIN.");
ImGui::Unindent(INDENT);
ImGui::TextUnformatted("");
}
void Config::build_buttons(const std::string &name, std::vector<Button> *buttons, int min, int max) { void Config::build_buttons(const std::string &name, std::vector<Button> *buttons, int min, int max) {
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "%s Buttons", name.c_str()); ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "%s Buttons", name.c_str());
@@ -633,14 +662,14 @@ namespace overlay::windows {
reset_button_to_default(&button, get_keypad_top_row(button)); reset_button_to_default(&button, get_keypad_top_row(button));
} }
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Remove All")) { if (ImGui::Button("Remove All")) {
for (auto &button : *buttons) { for (auto &button : *buttons) {
reset_button_to_default(&button, 0xFF); reset_button_to_default(&button, 0xFF);
} }
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
} }
} else { } else {
if (ImGui::Button("Yes")) { if (ImGui::Button("Yes")) {
for (auto &button : *buttons) { for (auto &button : *buttons) {
@@ -663,7 +692,7 @@ namespace overlay::windows {
// longest column is probably "Toggle Virtual Keypad P1" in Overlay tab // longest column is probably "Toggle Virtual Keypad P1" in Overlay tab
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(220)); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(220));
ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(140)); ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(170));
// check if empty // check if empty
if (!buttons || buttons->empty()) { if (!buttons || buttons->empty()) {
@@ -789,7 +818,7 @@ namespace overlay::windows {
// clear button // clear button
if (button_display.size() > 0 || alt_index > 0) { if (button_display.size() > 0 || alt_index > 0) {
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::DeleteButton(button_display.size() > 0 ? "Unbind" : "Delete")) { if (ImGui::DeleteButton(button_display.size() > 0 ? "Unbind" : "Delete")) {
clear_button(button, alt_index); clear_button(button, alt_index);
} }
} }
@@ -903,8 +932,8 @@ namespace overlay::windows {
// naive binding // naive binding
ImGui::SameLine(); ImGui::SameLine();
std::string naive_string = "Naive " + button_name; std::string naive_string = "Naive/XInput " + button_name;
if (ImGui::Button("Naive") if (ImGui::Button("Naive/XInput")
|| (buttons_many_active && buttons_many_active_section == name && !buttons_bind_active || (buttons_many_active && buttons_many_active_section == name && !buttons_bind_active
&& alt_index == 0 && alt_index == 0
&& buttons_many_naive && buttons_many_index == button_it && buttons_many_naive && buttons_many_index == button_it
@@ -924,11 +953,12 @@ namespace overlay::windows {
} }
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) { if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
ImGui::HelpTooltip( ImGui::HelpTooltip(
"Uses GetAsyncKeyState to check for any keyboard / mouse input. " "Alternative to Bind, using GetAsyncKeyState for KB/Mouse and XInput for controllers.\n\n"
"For best performance, Bind should be preferred, but this can be used when:\n" "This can be used when:\n"
" * you don't care about which device is used\n" " * you want to use XInput for gamepads (for analog triggers)\n"
" * you don't care about which keyboard/mouse is used\n"
" * you want to use input remapping or automation software\n" " * you want to use input remapping or automation software\n"
" * if you have NKRO issues with Bind"); " * you have NKRO issues with Bind");
} }
naive_button_popup(naive_string, button, button_it_max, alt_index); naive_button_popup(naive_string, button, button_it_max, alt_index);
@@ -988,7 +1018,7 @@ namespace overlay::windows {
} }
// Naive Many // Naive Many
if (ImGui::MenuItem("Naive (many)")) { if (ImGui::MenuItem("Naive/XInput (many)")) {
buttons_many_active = true; buttons_many_active = true;
buttons_many_index = button_it; buttons_many_index = button_it;
buttons_many_naive = true; buttons_many_naive = true;
@@ -1069,7 +1099,7 @@ namespace overlay::windows {
{"P1 Keypad 00", VK_OEM_MINUS}, {"P1 Keypad 00", VK_OEM_MINUS},
{"P1 Keypad Decimal", VK_OEM_PLUS}, {"P1 Keypad Decimal", VK_OEM_PLUS},
{"P1 Keypad Insert Card", VK_BACK}}; {"P1 Keypad Insert Card", VK_BACK}};
for (const auto &[key, value] : keypad_top_row_defaults) { for (const auto &[key, value] : keypad_top_row_defaults) {
if (button.getName() == key) { if (button.getName() == key) {
return value; return value;
@@ -1423,7 +1453,7 @@ namespace overlay::windows {
} }
for (unsigned short ch = 0; ch < midi->pitch_bend.size(); ch++) { for (unsigned short ch = 0; ch < midi->pitch_bend.size(); ch++) {
// check pitch bend down // check pitch bend down
if (midi->pitch_bend[ch] < 0) { if (midi->pitch_bend[ch] < 0) {
// bind control // bind control
@@ -1545,6 +1575,22 @@ namespace overlay::windows {
} }
} }
// check xinput
if (check_devices) {
xinput::XINPUT_NEW_BUTTON xinput;
if (RI_MGR->XINPUT_MGR->get_any_button_pressed(xinput)) {
button->setDeviceIdentifier(xinput::get_device_desc(xinput.player));
button->setVKey(static_cast<uint16_t>(xinput.button));
::Config::getInstance().updateBinding(
games_list[games_selected], *button, alt_index - 1);
inc_buttons_many_index(button_it_max);
buttons_bind_active = false;
ImGui::CloseCurrentPopup();
check_devices = false;
}
}
// check GetAsyncKeyState
if (check_devices) { if (check_devices) {
// get new keyboard state // get new keyboard state
// these are async, and some keys generate multiple vKeys (e.g., VK_SHIFT, VK_LSHIFT) // these are async, and some keys generate multiple vKeys (e.g., VK_SHIFT, VK_LSHIFT)
@@ -1629,7 +1675,7 @@ namespace overlay::windows {
if (ImGui::Selectable("Empty (Naive)", button->isNaive())) { if (ImGui::Selectable("Empty (Naive)", button->isNaive())) {
button->setDeviceIdentifier(""); button->setDeviceIdentifier("");
dirty = true; dirty = true;
} }
if (button->isNaive()) { if (button->isNaive()) {
ImGui::SetItemDefaultFocus(); ImGui::SetItemDefaultFocus();
} }
@@ -1713,7 +1759,7 @@ namespace overlay::windows {
button->setVelocityThreshold(0); button->setVelocityThreshold(0);
dirty = true; dirty = true;
} }
type = button->getAnalogType(); type = button->getAnalogType();
int midi_channel = 0; int midi_channel = 0;
@@ -1760,7 +1806,7 @@ namespace overlay::windows {
} else if (0x20 <= midi_index && midi_index < 0x3F) { } else if (0x20 <= midi_index && midi_index < 0x3F) {
midi_index = 0x40; midi_index = 0x40;
// skip range [0x60, 0x65] // skip range [0x60, 0x65]
} else if (midi_index == 0x65) { } else if (midi_index == 0x65) {
midi_index = 0x5F; midi_index = 0x5F;
} else if (0x60 <= midi_index && midi_index < 0x65) { } else if (0x60 <= midi_index && midi_index < 0x65) {
@@ -1844,7 +1890,7 @@ namespace overlay::windows {
ImGui::TextDisabled("Min. %d%%", velocity_threshold * 100 / 127); ImGui::TextDisabled("Min. %d%%", velocity_threshold * 100 / 127);
} else if (type == ButtonAnalogType::BAT_MIDI_CTRL_PRECISION || } else if (type == ButtonAnalogType::BAT_MIDI_CTRL_PRECISION ||
type == ButtonAnalogType::BAT_MIDI_CTRL_SINGLE) { type == ButtonAnalogType::BAT_MIDI_CTRL_SINGLE) {
ImGui::TextUnformatted("\n"); ImGui::TextUnformatted("\n");
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
@@ -1912,7 +1958,7 @@ namespace overlay::windows {
"Direction", "Direction",
is_up ? "Pitch Up" : "Pitch Down", is_up ? "Pitch Up" : "Pitch Down",
ImGuiComboFlags_HeightSmall)) { ImGuiComboFlags_HeightSmall)) {
if (ImGui::Selectable("Pitch Up", is_up)) { if (ImGui::Selectable("Pitch Up", is_up)) {
button->setAnalogType(ButtonAnalogType::BAT_MIDI_PITCH_UP); button->setAnalogType(ButtonAnalogType::BAT_MIDI_PITCH_UP);
dirty = true; dirty = true;
@@ -2165,6 +2211,9 @@ namespace overlay::windows {
case rawinput::MIDI: case rawinput::MIDI:
this->analogs_devices.emplace_back(&device); this->analogs_devices.emplace_back(&device);
break; break;
case rawinput::XINPUT_GAMEPAD:
this->analogs_devices.emplace_back(&device);
break;
default: default:
continue; continue;
} }
@@ -2213,7 +2262,7 @@ namespace overlay::windows {
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker(device->name.c_str()); ImGui::HelpMarker(device->name.c_str());
} }
switch (device->type) { switch (device->type) {
case rawinput::MOUSE: { case rawinput::MOUSE: {
@@ -2283,12 +2332,21 @@ namespace overlay::windows {
if (device->midiInfo->pitch_bend_set[ch]) { if (device->midiInfo->pitch_bend_set[ch]) {
control_names.push_back(fmt::format("Pitch Ch.{}", ch + 1)); control_names.push_back(fmt::format("Pitch Ch.{}", ch + 1));
analogs_midi_indices.push_back( analogs_midi_indices.push_back(
ch + ch +
precision.size() + precision.size() +
single.size() + single.size() +
onoff.size()); onoff.size());
} }
} }
break;
}
case rawinput::XINPUT_GAMEPAD: {
for (int i = 0; i < static_cast<int>(xinput::XInputAnalogEnum::COUNT); i++) {
const auto v = static_cast<xinput::XInputAnalogEnum>(i);
control_names.push_back(xinput::get_analog_string(v));
}
break;
} }
default: default:
break; break;
@@ -2428,7 +2486,7 @@ namespace overlay::windows {
if (invert != analog.getInvert()) { if (invert != analog.getInvert()) {
analog.setInvert(invert); analog.setInvert(invert);
} }
if (this->analogs_devices_selected >= 0) { if (this->analogs_devices_selected >= 0) {
const auto device = this->analogs_devices.at(this->analogs_devices_selected); const auto device = this->analogs_devices.at(this->analogs_devices_selected);
if (device->type == rawinput::HID) { if (device->type == rawinput::HID) {
@@ -2725,6 +2783,7 @@ namespace overlay::windows {
case rawinput::PIUIO_DEVICE: case rawinput::PIUIO_DEVICE:
case rawinput::SMX_STAGE: case rawinput::SMX_STAGE:
case rawinput::SMX_DEDICAB: case rawinput::SMX_DEDICAB:
case rawinput::XINPUT_GAMEPAD:
this->auto_match_devices.emplace_back(&device); this->auto_match_devices.emplace_back(&device);
break; break;
default: default:
@@ -2783,7 +2842,7 @@ namespace overlay::windows {
bool table_begin = false; bool table_begin = false;
for (size_t i = 0; i < lights->size(); i++) { for (size_t i = 0; i < lights->size(); i++) {
auto &light = lights->at(i); auto &light = lights->at(i);
if (current_section != light.getCategory()) { if (current_section != light.getCategory()) {
current_section = light.getCategory(); current_section = light.getCategory();
if (table_begin) { if (table_begin) {
@@ -2791,7 +2850,7 @@ namespace overlay::windows {
} }
render_section_header(current_section.empty() ? "Uncategorized" : current_section); render_section_header(current_section.empty() ? "Uncategorized" : current_section);
table_begin = begin_lights_table(); table_begin = begin_lights_table();
if (!table_begin) { if (!table_begin) {
break; break;
@@ -2813,7 +2872,7 @@ namespace overlay::windows {
alt_index++; alt_index++;
} }
} }
if (table_begin) { if (table_begin) {
ImGui::EndTable(); ImGui::EndTable();
@@ -2834,7 +2893,7 @@ namespace overlay::windows {
// progress bar // progress bar
ImGui::TableNextColumn(); ImGui::TableNextColumn();
// light name // light name
if (alt_index == 0) { if (alt_index == 0) {
ImGui::ProgressBar(light_state, ImVec2(32.f, 0)); ImGui::ProgressBar(light_state, ImVec2(32.f, 0));
@@ -2865,7 +2924,7 @@ namespace overlay::windows {
// clear light // clear light
if (light_display.size() > 0 || alt_index > 0) { if (light_display.size() > 0 || alt_index > 0) {
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::DeleteButton(light_display.size() > 0 ? "Unbind" : "Delete")) { if (ImGui::DeleteButton(light_display.size() > 0 ? "Unbind" : "Delete")) {
clear_light(light, alt_index); clear_light(light, alt_index);
} }
} }
@@ -2891,6 +2950,7 @@ namespace overlay::windows {
case rawinput::PIUIO_DEVICE: case rawinput::PIUIO_DEVICE:
case rawinput::SMX_STAGE: case rawinput::SMX_STAGE:
case rawinput::SMX_DEDICAB: case rawinput::SMX_DEDICAB:
case rawinput::XINPUT_GAMEPAD:
this->lights_devices.emplace_back(&device); this->lights_devices.emplace_back(&device);
break; break;
default: default:
@@ -2948,7 +3008,7 @@ namespace overlay::windows {
games_list[games_selected], *light, games_list[games_selected], *light,
alt_index - 1); alt_index - 1);
} }
void Config::edit_light_popup(Light &primary_light, Light *light, const int alt_index) { void Config::edit_light_popup(Light &primary_light, Light *light, const int alt_index) {
if (ImGui::BeginPopupModal("Light Binding", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { if (ImGui::BeginPopupModal("Light Binding", NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
@@ -3044,6 +3104,15 @@ namespace overlay::windows {
} }
break; break;
} }
case rawinput::XINPUT_GAMEPAD: {
// add all names of XInput gamepad device
for (int i = 0; i < static_cast<int>(xinput::XInputOutputEnum::COUNT); i++) {
control_names.emplace_back(
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(i)));
}
break;
}
default: default:
break; break;
} }
@@ -3589,6 +3658,13 @@ namespace overlay::windows {
} }
break; break;
} }
case rawinput::XINPUT_GAMEPAD: {
for (int i = 0; i < static_cast<int>(xinput::XInputOutputEnum::COUNT); i++) {
raw_names.emplace_back(
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(i)));
}
break;
}
default: default:
break; break;
} }
@@ -3748,7 +3824,7 @@ namespace overlay::windows {
return detected_controller; return detected_controller;
} }
void Config::build_cards() { void Config::build_cards() {
constexpr float TEXT_INPUT_WIDTH = 240.f; constexpr float TEXT_INPUT_WIDTH = 240.f;
@@ -4078,12 +4154,12 @@ namespace overlay::windows {
// verify card number // verify card number
this->keypads_card_file_contents_valid[player] = true; this->keypads_card_file_contents_valid[player] = true;
if (this->keypads_card_number[player][0] != 0) { if (this->keypads_card_number[player][0] != 0) {
this->keypads_card_file_contents_valid[player] = this->keypads_card_file_contents_valid[player] =
validate_ea_card(this->keypads_card_number[player]); validate_ea_card(this->keypads_card_number[player]);
} }
// card number box // card number box
ImGui::SetNextItemWidth(TEXT_INPUT_WIDTH); ImGui::SetNextItemWidth(TEXT_INPUT_WIDTH);
ImGui::BeginDisabled(); ImGui::BeginDisabled();
if (this->keypads_card_number[player][0] != 0) { if (this->keypads_card_number[player][0] != 0) {
@@ -4289,7 +4365,9 @@ namespace overlay::windows {
ImGui::TextUnformatted("Requires restart! Showing all procs in Group 0."); ImGui::TextUnformatted("Requires restart! Showing all procs in Group 0.");
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
ImGui::TextUnformatted("Pick CPU cores to use:"); ImGui::TextUnformatted("Pick CPU cores to use:");
const uint64_t cpu_count = GetActiveProcessorCount(0); SYSTEM_INFO info;
GetSystemInfo(&info);
const uint64_t cpu_count = info.dwNumberOfProcessors;
uint64_t affinity = 0; uint64_t affinity = 0;
if (!option.value.empty()) { if (!option.value.empty()) {
try { try {
@@ -4333,7 +4411,7 @@ namespace overlay::windows {
option.value = fmt::format("0x{:X}", affinity); option.value = fmt::format("0x{:X}", affinity);
} }
} }
ImGui::EndChild(); ImGui::EndChild();
if (option.value.empty()) { if (option.value.empty()) {
ImGui::TextUnformatted("Using all CPUs (option default)."); ImGui::TextUnformatted("Using all CPUs (option default).");
@@ -4481,9 +4559,11 @@ namespace overlay::windows {
for (const auto &monitor : monitors) { for (const auto &monitor : monitors) {
const bool is_selected = option.value == monitor.display_name; const bool is_selected = option.value == monitor.display_name;
auto friendly = wchar_to_u8(monitor.friendly_name.c_str()); auto friendly = wchar_to_u8(monitor.friendly_name.c_str());
if (ImGui::Selectable( auto str = fmt::format("{} ({})", monitor.display_name, friendly);
fmt::format("{} ({})", monitor.display_name, friendly).c_str(), if (monitor.is_primary) {
is_selected)) { str += " [Primary]";
}
if (ImGui::Selectable(str.c_str(), is_selected)) {
option.value = monitor.display_name; option.value = monitor.display_name;
} }
} }
@@ -4499,7 +4579,7 @@ namespace overlay::windows {
void Config::build_options( void Config::build_options(
std::vector<Option> *options, const std::string &category, const std::string *filter) { std::vector<Option> *options, const std::string &category, const std::string *filter) {
int options_count; int options_count;
// category name // category name
std::string cat = "Options"; std::string cat = "Options";
@@ -4525,19 +4605,19 @@ namespace overlay::windows {
widget_col_width = overlay::apply_scaling(264); widget_col_width = overlay::apply_scaling(264);
} }
ImGui::TableSetupColumn("Setting", ImGuiTableColumnFlags_WidthFixed, widget_col_width); ImGui::TableSetupColumn("Setting", ImGuiTableColumnFlags_WidthFixed, widget_col_width);
// iterate options // iterate options
options_count = 0; options_count = 0;
for (auto &option : *options) { for (auto &option : *options) {
// get option definition // get option definition
auto &definition = option.get_definition(); auto &definition = option.get_definition();
// check category // check category
if (!category.empty() && definition.category != category) { if (!category.empty() && definition.category != category) {
continue; continue;
} }
// check hidden option // check hidden option
if (!this->options_show_hidden && option.value.empty()) { if (!this->options_show_hidden && option.value.empty()) {
// skip hidden entries // skip hidden entries
@@ -4552,7 +4632,7 @@ namespace overlay::windows {
continue; continue;
} }
} }
// filter // filter
if (filter != nullptr) { if (filter != nullptr) {
if (filter->empty()) { if (filter->empty()) {
@@ -4568,7 +4648,7 @@ namespace overlay::windows {
} }
options_count += 1; options_count += 1;
// list entry // list entry
ImGui::PushID(&option); ImGui::PushID(&option);
ImGui::TableNextRow(); ImGui::TableNextRow();
@@ -4580,10 +4660,10 @@ namespace overlay::windows {
if (definition.category.empty()) { if (definition.category.empty()) {
ImGui::TextDisabled("-"); ImGui::TextDisabled("-");
} else { } else {
ImGui::TextDisabled(definition.category.c_str()); ImGui::TextDisabled("%s", definition.category.c_str());
} }
} }
// option name // option name
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
@@ -4763,7 +4843,7 @@ namespace overlay::windows {
if (!element.second.empty()) { if (!element.second.empty()) {
label += fmt::format(" ({})", element.second); label += fmt::format(" ({})", element.second);
} }
bool selected = current_item == label; bool selected = current_item == label;
if (ImGui::Selectable(label.c_str(), selected)) { if (ImGui::Selectable(label.c_str(), selected)) {
this->options_dirty = true; this->options_dirty = true;
@@ -4834,7 +4914,7 @@ namespace overlay::windows {
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "%s", definition.title.c_str()); ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "%s", definition.title.c_str());
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
ImGui::TextUnformatted("Current value:"); ImGui::TextUnformatted("Current value:");
ImGui::BeginDisabled(); ImGui::BeginDisabled();
// keeping it read only; if you want to make this editable, we need to refactor the // keeping it read only; if you want to make this editable, we need to refactor the
@@ -4856,7 +4936,7 @@ namespace overlay::windows {
if (ImGui::Button("Save & Close")) { if (ImGui::Button("Save & Close")) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
this->options_dirty = true; this->options_dirty = true;
::Config::getInstance().updateBinding(games_list[games_selected], option); ::Config::getInstance().updateBinding(games_list[games_selected], option);
} }
ImGui::EndPopup(); ImGui::EndPopup();
} }
@@ -4864,7 +4944,7 @@ namespace overlay::windows {
// row hover detection (invisible selectable that spans entire row) // row hover detection (invisible selectable that spans entire row)
ImGui::InvisibleTableRowSelectable(); ImGui::InvisibleTableRowSelectable();
// next item // next item
ImGui::PopID(); ImGui::PopID();
} }
@@ -4872,12 +4952,24 @@ namespace overlay::windows {
// check if empty // check if empty
if (options_count == 0) { if (options_count == 0) {
ImGui::TableNextRow(); ImGui::TableNextRow();
// option category
if (filter != nullptr) {
ImGui::TableNextColumn();
ImGui::TextDisabled("-");
}
// name of option
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Indent(INDENT); if (filter == nullptr) {
ImGui::TextDisabled("-"); ImGui::Indent(INDENT);
ImGui::Unindent(INDENT); }
ImGui::TableNextColumn();
ImGui::TextDisabled("-"); ImGui::TextDisabled("-");
if (filter == nullptr) {
ImGui::Unindent(INDENT);
}
// widget
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::TextDisabled("-"); ImGui::TextDisabled("-");
} }
@@ -4888,14 +4980,19 @@ namespace overlay::windows {
} }
void Config::build_about() { void Config::build_about() {
#ifndef SPICE_LINUX #if SPICE_LINUX
ImGui::TextUnformatted(std::string( ImGui::TextUnformatted(std::string(
"spice2x (a fork of SpiceTools)\r\n" "spice2x (a fork of SpiceTools) for Linux\r\n"
"=========================\r\n" +
to_string(VERSION_STRING)).c_str());
#elif SPICE_XP
ImGui::TextUnformatted(std::string(
"spice2x (a fork of SpiceTools) for WinXP\r\n"
"=========================\r\n" + "=========================\r\n" +
to_string(VERSION_STRING)).c_str()); to_string(VERSION_STRING)).c_str());
#else #else
ImGui::TextUnformatted(std::string( ImGui::TextUnformatted(std::string(
"spice2x (a fork of SpiceTools) for Linux\r\n" "spice2x (a fork of SpiceTools)\r\n"
"=========================\r\n" + "=========================\r\n" +
to_string(VERSION_STRING)).c_str()); to_string(VERSION_STRING)).c_str());
#endif #endif
@@ -4904,7 +5001,7 @@ namespace overlay::windows {
if (ImGui::TextLink(PROJECT_URL)) { if (ImGui::TextLink(PROJECT_URL)) {
launch_shell(PROJECT_URL); launch_shell(PROJECT_URL);
} }
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
ImGui::TextUnformatted(resutil::load_file_string_crlf(IDR_README).c_str()); ImGui::TextUnformatted(resutil::load_file_string_crlf(IDR_README).c_str());
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
@@ -5012,7 +5109,7 @@ namespace overlay::windows {
const ImVec2 popup_pos( const ImVec2 popup_pos(
ImGui::GetIO().DisplaySize.x / 2 - popup_size.x / 2, ImGui::GetIO().DisplaySize.x / 2 - popup_size.x / 2,
ImGui::GetIO().DisplaySize.y / 2 - popup_size.y / 2); ImGui::GetIO().DisplaySize.y / 2 - popup_size.y / 2);
ImGui::SetNextWindowSize(popup_size, ImGuiCond_Appearing); ImGui::SetNextWindowSize(popup_size, ImGuiCond_Appearing);
ImGui::SetNextWindowPos(popup_pos, ImGuiCond_Appearing); ImGui::SetNextWindowPos(popup_pos, ImGuiCond_Appearing);
bool unused_open2 = true; bool unused_open2 = true;
@@ -5679,7 +5776,7 @@ namespace overlay::windows {
ImGui::EndTable(); ImGui::EndTable();
} }
if (!apply_buttons && !apply_keypads && !apply_analogs && !apply_lights) { if (!apply_buttons && !apply_keypads && !apply_analogs && !apply_lights) {
ImGui::EndDisabled(); ImGui::EndDisabled();
} }
+5 -3
View File
@@ -193,13 +193,15 @@ namespace overlay::windows {
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr); std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
void build_about(); void build_about();
void build_launcher(); void build_launcher();
void launch_shell(LPCSTR app, LPCSTR file=nullptr); void build_keypad_warning();
void launch_shell(LPCSTR app, LPCSTR file=nullptr);
void build_menu(int *game_selected); void build_menu(int *game_selected);
void shutdown_system(bool force, bool reboot_instead); void shutdown_system(bool force, bool reboot_instead);
void set_alternating_row_colors(const int row_index); void set_alternating_row_colors(const int row_index);
bool validate_ea_card(char card_number[16]); bool validate_ea_card(char card_number[16]);
public: public:
+13 -10
View File
@@ -628,13 +628,12 @@ namespace overlay::windows {
ImGui::Text("Wheel: %ld", mouse->pos_wheel); ImGui::Text("Wheel: %ld", mouse->pos_wheel);
// keys // keys
std::stringstream keys; std::string keys = "[";
keys << "[";
for (auto key : mouse->key_states) { for (auto key : mouse->key_states) {
keys << (key ? "1," : "0,"); keys += (key ? "1," : "0,");
} }
keys << "]"; keys += "]";
ImGui::Text("Keys: %s", keys.str().c_str()); ImGui::Text("Keys: %s", keys.c_str());
break; break;
} }
@@ -643,15 +642,14 @@ namespace overlay::windows {
ImGui::Text("Type: Keyboard"); ImGui::Text("Type: Keyboard");
// keys // keys
std::stringstream keys; std::string keys = "[";
keys << "[";
for (size_t i = 0; i < std::size(keyboard->key_states); i++) { for (size_t i = 0; i < std::size(keyboard->key_states); i++) {
if (keyboard->key_states[i]) { if (keyboard->key_states[i]) {
keys << i << ","; keys += std::to_string(i) + ",";
} }
} }
keys << "]"; keys += "]";
ImGui::Text("Keys: %s", keys.str().c_str()); ImGui::Text("Keys: %s", keys.c_str());
break; break;
} }
@@ -732,6 +730,10 @@ namespace overlay::windows {
ImGui::Text("Type: PIUIO"); ImGui::Text("Type: PIUIO");
break; break;
} }
case rawinput::XINPUT_GAMEPAD: {
ImGui::Text("Type: XInput");
break;
}
case rawinput::DESTROYED: { case rawinput::DESTROYED: {
ImGui::Text("Disconnected."); ImGui::Text("Disconnected.");
break; break;
@@ -739,6 +741,7 @@ namespace overlay::windows {
case rawinput::UNKNOWN: case rawinput::UNKNOWN:
default: default:
ImGui::Text("Type: Unknown"); ImGui::Text("Type: Unknown");
break;
} }
ImGui::TreePop(); ImGui::TreePop();
} }
@@ -4,6 +4,7 @@
#include "util/logging.h" #include "util/logging.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/popn/popn.h"
namespace overlay::windows { namespace overlay::windows {
@@ -72,6 +73,8 @@ namespace overlay::windows {
sub = "Show DRS Dance Floor"; sub = "Show DRS Dance Floor";
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) { } else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
sub = "Show GITADORA Subscreen"; sub = "Show GITADORA Subscreen";
} else if (games::popn::is_pikapika_model()) {
sub = "Show Pop'n Subscreen";
} }
build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE, false); build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE, false);
+1 -1
View File
@@ -14,7 +14,7 @@ namespace overlay::windows {
if (GRAPHICS_IIDX_WSUB) { if (GRAPHICS_IIDX_WSUB) {
this->disabled_message = "Close this overlay and use the second window. (try ALT+TAB)"; this->disabled_message = "Close this overlay and use the second window. (try ALT+TAB)";
} else if (games::iidx::IIDX_TDJ_MONITOR_WARNING) { } else if (games::iidx::IIDX_TDJ_MONITOR_WARNING) {
this->disabled_message = "TDJ mode subscreen overlay is not compatible with -monitor option."; this->disabled_message = "TDJ mode subscreen overlay is not compatible with -dxmainadapter option; use -mainmonitor instead";
} }
float size = 0.5f; float size = 0.5f;
+53
View File
@@ -0,0 +1,53 @@
#undef CINTERFACE
#include "avs/game.h"
#include "popn_sub.h"
#include "games/popn/popn.h"
#include "hooks/graphics/graphics.h"
#include "touch/touch.h"
namespace overlay::windows {
PopnSubScreen::PopnSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
this->title = "Pop'n Subscreen";
if (!games::popn::is_pikapika_model()) {
this->disabled_message = "Game did not launch as Pikapika Pop-kun (invalid <spec>)!";
} else if (games::popn::SHOW_PIKA_MONITOR_WARNING) {
this->disabled_message = "Subscreen overlay is not compatible with -dxmainadapter option, use -mainmonitor instead";
} else if (GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOW) {
this->disabled_message = "Subscren overlay was not enabled in spicecfg. Use the second window (ALT+TAB).";
}
this->resize_callback = keep_16_by_10;
float size = 0.5f;
this->init_size = ImVec2(
ImGui::GetIO().DisplaySize.x * size,
(ImGui::GetIO().DisplaySize.x * size * 10 / 16) + ImGui::GetFrameHeight());
this->size_max = ImVec2(
ImGui::GetIO().DisplaySize.x - ImGui::GetFrameHeight() * 2,
ImGui::GetIO().DisplaySize.y - ImGui::GetFrameHeight() * 2);
// middle / bottom
this->init_pos = ImVec2(
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
ImGui::GetIO().DisplaySize.y - this->init_size.y - (ImGui::GetFrameHeight() / 2));
}
void PopnSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {
if (!this->get_active()) {
return;
}
if (GRAPHICS_WINDOWED) {
// Touch needs to be registered on global coords
*x_out = SPICETOUCH_TOUCH_X + xy_in.x * SPICETOUCH_TOUCH_WIDTH;
*y_out = SPICETOUCH_TOUCH_Y + xy_in.y * SPICETOUCH_TOUCH_HEIGHT;
} else {
// Fullscreen mode, scale to game coords
*x_out = xy_in.x * ImGui::GetIO().DisplaySize.x;
*y_out = xy_in.y * ImGui::GetIO().DisplaySize.y;
}
}
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <windows.h>
#include "overlay/window.h"
#include "overlay/windows/generic_sub.h"
namespace overlay::windows {
class PopnSubScreen : public GenericSubScreen {
public:
PopnSubScreen(SpiceOverlay *overlay);
protected:
void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) override;
private:
static void keep_16_by_10(ImGuiSizeCallbackData* data) {
data->DesiredSize.y = (data->DesiredSize.x * 10.f / 16.f) + ImGui::GetFrameHeight();
}
};
}
@@ -213,7 +213,7 @@ namespace overlay::windows {
ImGui::SameLine(); ImGui::SameLine();
ImGui::HelpMarker( ImGui::HelpMarker(
"Change window decoration. Resizable Window may not cause your mouse cursor to change, " "Change window decoration. Resizable Window may not cause your mouse cursor to change, "
"but you can still drag to resize. Disabled for some games due to incompatibility."); "but you can still drag to resize. Disabled for some games due to incompatibility; change in spicecfg instead.");
if (ImGui::Checkbox("Always On Top", &cfg::SCREENRESIZE->window_always_on_top) ) { if (ImGui::Checkbox("Always On Top", &cfg::SCREENRESIZE->window_always_on_top) ) {
graphics_update_z_order(window, cfg::SCREENRESIZE->window_always_on_top); graphics_update_z_order(window, cfg::SCREENRESIZE->window_always_on_top);
+1 -1
View File
@@ -13,7 +13,7 @@ namespace overlay::windows {
if (!games::sdvx::is_valkyrie_model()) { if (!games::sdvx::is_valkyrie_model()) {
this->disabled_message = "Valkyrie Model mode is not enabled!"; this->disabled_message = "Valkyrie Model mode is not enabled!";
} else if (games::sdvx::SHOW_VM_MONITOR_WARNING) { } else if (games::sdvx::SHOW_VM_MONITOR_WARNING) {
this->disabled_message = "VM mode subscreen overlay is not compatible with -monitor option"; this->disabled_message = "VM mode subscreen overlay is not compatible with -dxmainadapter option, use -mainmonitor instead";
} else if (GRAPHICS_WINDOWED) { } else if (GRAPHICS_WINDOWED) {
if (GRAPHICS_PREVENT_SECONDARY_WINDOW) { if (GRAPHICS_PREVENT_SECONDARY_WINDOW) {
this->disabled_message = "Subscreen has been disabled by the user (-sdvxnosub)."; this->disabled_message = "Subscreen has been disabled by the user (-sdvxnosub).";
+2 -1
View File
@@ -31,7 +31,8 @@ namespace rawinput {
SEXTET_OUTPUT, SEXTET_OUTPUT,
PIUIO_DEVICE, PIUIO_DEVICE,
SMX_STAGE, SMX_STAGE,
SMX_DEDICAB SMX_DEDICAB,
XINPUT_GAMEPAD,
}; };
enum MouseKeys { enum MouseKeys {
+3 -1
View File
@@ -119,8 +119,10 @@ namespace rawinput {
// success // success
return TRUE; return TRUE;
} }
case 7: { // windows 10 reports this for MIDI? case DBT_DEVNODES_CHANGED: {
// TODO: this can be a little noisy as it gets called on every device
this->ri_mgr->devices_scan_midi(); this->ri_mgr->devices_scan_midi();
this->ri_mgr->devices_scan_xinput();
break; break;
} }
} }
+88
View File
@@ -8,6 +8,7 @@
#include <setupapi.h> #include <setupapi.h>
#include "util/logging.h" #include "util/logging.h"
#include "external/robin_hood.h"
#include "util/time.h" #include "util/time.h"
#include "util/utils.h" #include "util/utils.h"
@@ -70,6 +71,8 @@ void rawinput::set_midi_algorithm(rawinput::MidiNoteAlgorithm new_algo) {
rawinput::RawInputManager::RawInputManager() { rawinput::RawInputManager::RawInputManager() {
XINPUT_MGR = std::make_unique<xinput::XInputManager>();
// create input window and load in devices // create input window and load in devices
this->input_hwnd_create(); this->input_hwnd_create();
this->devices_reload(); this->devices_reload();
@@ -106,6 +109,8 @@ void rawinput::RawInputManager::stop() {
// destruct all devices and input window // destruct all devices and input window
this->devices_destruct(); this->devices_destruct();
this->input_hwnd_destroy(); this->input_hwnd_destroy();
XINPUT_MGR.reset();
} }
void rawinput::RawInputManager::input_hwnd_create() { void rawinput::RawInputManager::input_hwnd_create() {
@@ -191,6 +196,8 @@ void rawinput::RawInputManager::devices_reload() {
this->devices_scan_smxdedicab(); this->devices_scan_smxdedicab();
} }
this->devices_scan_xinput();
// check for LIT Board // check for LIT Board
sextet_register("COM54", "LIT Board", false); sextet_register("COM54", "LIT Board", false);
@@ -931,6 +938,7 @@ void rawinput::RawInputManager::devices_scan_piuio() {
// add device to vector first so pointer is valid // add device to vector first so pointer is valid
auto *new_piuio_device = new Device(); auto *new_piuio_device = new Device();
new_piuio_device->id = this->devices.size() + 1;
new_piuio_device->type = PIUIO_DEVICE; new_piuio_device->type = PIUIO_DEVICE;
new_piuio_device->name = "piuio"; new_piuio_device->name = "piuio";
new_piuio_device->desc = "PIUIO"; new_piuio_device->desc = "PIUIO";
@@ -961,6 +969,7 @@ void rawinput::RawInputManager::devices_scan_smxstage() {
log_misc("rawinput", "scan SMX Stage devices..."); log_misc("rawinput", "scan SMX Stage devices...");
auto *new_smxstage_device = new Device(); auto *new_smxstage_device = new Device();
new_smxstage_device->id = this->devices.size() + 1;
new_smxstage_device->type = SMX_STAGE; new_smxstage_device->type = SMX_STAGE;
new_smxstage_device->name = "smxstage"; new_smxstage_device->name = "smxstage";
new_smxstage_device->desc = "SMX Stage"; new_smxstage_device->desc = "SMX Stage";
@@ -987,6 +996,7 @@ void rawinput::RawInputManager::devices_scan_smxdedicab() {
log_misc("rawinput", "scan SMX Dedicated Cabinet devices..."); log_misc("rawinput", "scan SMX Dedicated Cabinet devices...");
auto *new_smxdedicab_device = new Device(); auto *new_smxdedicab_device = new Device();
new_smxdedicab_device->id = this->devices.size() + 1;
new_smxdedicab_device->type = SMX_DEDICAB; new_smxdedicab_device->type = SMX_DEDICAB;
new_smxdedicab_device->name = "smxdedicab"; new_smxdedicab_device->name = "smxdedicab";
new_smxdedicab_device->desc = "SMX Dedicated Cabinet"; new_smxdedicab_device->desc = "SMX Dedicated Cabinet";
@@ -1009,6 +1019,76 @@ void rawinput::RawInputManager::devices_scan_smxdedicab() {
} }
} }
void rawinput::RawInputManager::devices_scan_xinput() {
log_misc("rawinput", "scan XInput devices...");
const auto connected_players = XINPUT_MGR->get_available_players();
// first, destroy missing devices
std::vector<std::string> devices_to_remove;
for (auto &device : this->devices) {
if (device.type != XINPUT_GAMEPAD) {
continue;
}
const uint8_t player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device.handle));
if (std::find(connected_players.begin(), connected_players.end(), player) == connected_players.end()) {
devices_to_remove.push_back(device.name);
}
}
for (const auto &name : devices_to_remove) {
this->devices_remove(name);
}
auto create_device = [](const uint8_t player) -> Device {
Device device = {};
device.type = XINPUT_GAMEPAD;
device.name = xinput::get_device_desc(player);
device.desc = fmt::format("XInput Gamepad P{}", player + 1);
device.handle = reinterpret_cast<HANDLE>(player);
device.mutex = new std::mutex();
device.mutex_out = new std::mutex();
return device;
};
// add new devices
for (const auto player : connected_players) {
bool duplicate_found = false;
// check for duplicates first
for (auto &prev_device : this->devices) {
if (prev_device.name != xinput::get_device_desc(player)) {
continue;
}
if (prev_device.type == DESTROYED) {
log_info("rawinput", "overwriting previously destroyed XInput device: {}", prev_device.name);
const auto old_id = prev_device.id;
prev_device = create_device(player);
prev_device.id = old_id;
// notify change
for (auto &cb : this->callback_change) {
cb.f(cb.data, &prev_device);
}
}
duplicate_found = true;
break;
}
if (!duplicate_found) {
// add new device
log_info("rawinput", "adding new XInput device: player {}", player + 1);
auto new_xinput_device = create_device(player);
new_xinput_device.id = this->devices.size() + 1;
auto &device = this->devices.emplace_back(new_xinput_device);
// notify add
for (auto &cb : this->callback_add) {
cb.f(cb.data, &device);
}
}
}
}
void rawinput::RawInputManager::flush_start() { void rawinput::RawInputManager::flush_start() {
// start flush thread // start flush thread
@@ -2567,6 +2647,10 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
device->smxdedicabInfo->Update(); device->smxdedicabInfo->Update();
break; break;
} }
case XINPUT_GAMEPAD: {
// nothing - updates to these are instant
break;
}
default: default:
break; break;
} }
@@ -2734,6 +2818,10 @@ void rawinput::RawInputManager::devices_print() {
log_misc("rawinput", "device type: SMX_DEDICAB"); log_misc("rawinput", "device type: SMX_DEDICAB");
break; break;
} }
case XINPUT_GAMEPAD: {
log_misc("rawinput", "device type: XINPUT");
break;
}
case UNKNOWN: case UNKNOWN:
default: default:
log_warning("rawinput", "device type: UNKNOWN"); log_warning("rawinput", "device type: UNKNOWN");
+3
View File
@@ -10,6 +10,7 @@
#include "device.h" #include "device.h"
#include "hotplug.h" #include "hotplug.h"
#include "rawinput/xinput.h"
#include "util/scope_guard.h" #include "util/scope_guard.h"
namespace rawinput { namespace rawinput {
@@ -101,6 +102,7 @@ namespace rawinput {
public: public:
HWND input_hwnd = nullptr; HWND input_hwnd = nullptr;
std::unique_ptr<xinput::XInputManager> XINPUT_MGR = nullptr;
RawInputManager(); RawInputManager();
~RawInputManager(); ~RawInputManager();
@@ -114,6 +116,7 @@ namespace rawinput {
void devices_scan_rawinput(const std::string &device_name = ""); void devices_scan_rawinput(const std::string &device_name = "");
void devices_scan_midi(); void devices_scan_midi();
void devices_scan_xinput();
void devices_remove(const std::string &name); void devices_remove(const std::string &name);
void devices_register(); void devices_register();
+8 -11
View File
@@ -192,8 +192,10 @@ namespace rawinput::touch {
contact_count = std::max(contact_count, (size_t) hid->value_states_raw[index]); contact_count = std::max(contact_count, (size_t) hid->value_states_raw[index]);
} }
// https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/touchscreen-packet-reporting-modes
// hybrid mode devices will report a contact count of 0 for subsequent reports that are // hybrid mode devices will report a contact count of 0 for subsequent reports that are
// part of the same initial frame // part of the same initial frame
// most laptops and touchscreens are like this - they have the contact count set to 5 but can actually report up to 10 fingers
if (contact_count > 0) { if (contact_count > 0) {
if (contact_count > touch_report_count) { if (contact_count > touch_report_count) {
touch.remaining_contact_count = contact_count - touch_report_count; touch.remaining_contact_count = contact_count - touch_report_count;
@@ -216,6 +218,12 @@ namespace rawinput::touch {
touch_points.reserve(touch.elements_x.size()); touch_points.reserve(touch.elements_x.size());
for (size_t i = 0; i < touch.elements_x.size(); i++) { for (size_t i = 0; i < touch.elements_x.size(); i++) {
// if there are no more touch events to handle, exit out early
if (touch_report_count == 0) {
break;
}
touch_report_count--;
// build touch point // build touch point
HIDTouchPoint hid_tp{}; HIDTouchPoint hid_tp{};
auto pos_x = hid->value_states[touch.elements_x[i]]; auto pos_x = hid->value_states[touch.elements_x[i]];
@@ -247,17 +255,6 @@ namespace rawinput::touch {
hid_tp.y = 1.f - hid_tp.y; hid_tp.y = 1.f - hid_tp.y;
} }
// check if this touch point should be considered valid
//
// If "Contact count" reports there are no touch reports remaining and the X and Y
// coordinates of this touch point are zero, then this report element should be
// skipped.
if (touch_report_count == 0 && hid_tp.x == 0.f && hid_tp.y == 0.f) {
continue;
} else if (touch_report_count > 0) {
touch_report_count--;
}
// generate ID (hopefully unique) // generate ID (hopefully unique)
hid_tp.id = (DWORD) hid->value_states_raw[touch.elements_contact_identifier[i]]; hid_tp.id = (DWORD) hid->value_states_raw[touch.elements_contact_identifier[i]];
hid_tp.id += (DWORD) (0xFFFFFF + device->id * 512); hid_tp.id += (DWORD) (0xFFFFFF + device->id * 512);
+407
View File
@@ -0,0 +1,407 @@
#include "rawinput/xinput.h"
#include "util/logging.h"
namespace xinput {
// this is all we need to emulate xinput.h which we avoid including here...
#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD (30 / 255.0f)
#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849
#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
#define XINPUT_GAMEPAD_DPAD_UP 0x0001
#define XINPUT_GAMEPAD_DPAD_DOWN 0x0002
#define XINPUT_GAMEPAD_DPAD_LEFT 0x0004
#define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008
#define XINPUT_GAMEPAD_START 0x0010
#define XINPUT_GAMEPAD_BACK 0x0020
#define XINPUT_GAMEPAD_LEFT_THUMB 0x0040
#define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080
#define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100
#define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200
#define XINPUT_GAMEPAD_A 0x1000
#define XINPUT_GAMEPAD_B 0x2000
#define XINPUT_GAMEPAD_X 0x4000
#define XINPUT_GAMEPAD_Y 0x8000
typedef struct {
uint32_t dwPacketNumber;
XINPUT_GAMEPAD_STATE Gamepad;
} XINPUT_STATE;
DWORD
WINAPI
XInputGetState(
DWORD dwUserIndex,
XINPUT_STATE *pState
);
typedef struct {
uint16_t wLeftMotorSpeed;
uint16_t wRightMotorSpeed;
} XINPUT_VIBRATION;
DWORD
WINAPI
XInputSetState(
DWORD dwUserIndex,
XINPUT_VIBRATION* pVibration
);
// end xinput definitions
std::string get_button_string(XInputButtonEnum button) {
switch (button) {
case XInputButtonEnum::DPAD_UP:
return "Dpad Up";
case XInputButtonEnum::DPAD_DOWN:
return "Dpad Down";
case XInputButtonEnum::DPAD_LEFT:
return "Dpad Left";
case XInputButtonEnum::DPAD_RIGHT:
return "Dpad Right";
case XInputButtonEnum::START:
return "Start";
case XInputButtonEnum::BACK:
return "Back";
case XInputButtonEnum::LEFT_STICK:
return "Left Stick Click";
case XInputButtonEnum::RIGHT_STICK:
return "Right Stick Click";
case XInputButtonEnum::LEFT_SHOULDER:
return "Left Bumper";
case XInputButtonEnum::RIGHT_SHOULDER:
return "Right Bumper";
case XInputButtonEnum::BUTTON_A:
return "Button A";
case XInputButtonEnum::BUTTON_B:
return "Button B";
case XInputButtonEnum::BUTTON_X:
return "Button X";
case XInputButtonEnum::BUTTON_Y:
return "Button Y";
case XInputButtonEnum::LEFT_TRIGGER:
return "Left Trigger";
case XInputButtonEnum::RIGHT_TRIGGER:
return "Right Trigger";
case XInputButtonEnum::LEFT_STICK_UP:
return "Left Stick, Up";
case XInputButtonEnum::LEFT_STICK_DOWN:
return "Left Stick, Down";
case XInputButtonEnum::LEFT_STICK_LEFT:
return "Left Stick, Left";
case XInputButtonEnum::LEFT_STICK_RIGHT:
return "Left Stick, Right";
case XInputButtonEnum::RIGHT_STICK_UP:
return "Right Stick, Up";
case XInputButtonEnum::RIGHT_STICK_DOWN:
return "Right Stick, Down";
case XInputButtonEnum::RIGHT_STICK_LEFT:
return "Right Stick, Left";
case XInputButtonEnum::RIGHT_STICK_RIGHT:
return "Right Stick, Right";
default:
break;
}
return fmt::format("Unknown Button ({})", static_cast<int>(button));
}
std::string get_analog_string(XInputAnalogEnum analog) {
switch (analog) {
case XInputAnalogEnum::LEFT_TRIGGER:
return "Left Trigger";
case XInputAnalogEnum::RIGHT_TRIGGER:
return "Right Trigger";
case XInputAnalogEnum::LEFT_STICK_X:
return "Left Stick X";
case XInputAnalogEnum::LEFT_STICK_Y:
return "Left Stick Y";
case XInputAnalogEnum::RIGHT_STICK_X:
return "Right Stick X";
case XInputAnalogEnum::RIGHT_STICK_Y:
return "Right Stick Y";
default:
break;
}
return fmt::format("Unknown Analog ({})", static_cast<int>(analog));
}
std::string get_output_string(XInputOutputEnum output) {
switch (output) {
case XInputOutputEnum::LEFT_RUMBLE:
return "Left Rumble";
case XInputOutputEnum::RIGHT_RUMBLE:
return "Right Rumble";
default:
break;
}
return fmt::format("Unknown Output ({})", static_cast<int>(output));
}
std::string get_device_desc(uint8_t player) {
return fmt::format(";XINPUT;{}", player);
}
#if defined(SPICE_XP)
XInputManager::XInputManager() {}
XInputManager::~XInputManager() {}
void XInputManager::stop() {}
std::vector<uint8_t> XInputManager::get_available_players() {
return {};
}
float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) {
return 0.5f;
}
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) {
return false;
}
bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) {
return false;
}
void XInputManager::set_output_state(uint8_t player, XInputOutputEnum output, float value) {
return;
}
#else
static decltype(XInputGetState) *XInputGetState_addr = nullptr;
static decltype(XInputSetState) *XInputSetState_addr = nullptr;
XInputManager::XInputManager() {
log_info("xinput", "initialize...");
this->xinput_lib = LoadLibraryA("xinput1_3.dll");
if (!this->xinput_lib) {
log_warning("xinput", "failed to load xinput1_3.dll");
return;
}
XInputGetState_addr = reinterpret_cast<decltype(XInputGetState) *>(
GetProcAddress(this->xinput_lib, "XInputGetState"));
if (!XInputGetState_addr) {
log_warning("xinput", "failed to get XInputGetState address");
this->stop();
return;
}
XInputSetState_addr = reinterpret_cast<decltype(XInputSetState) *>(
GetProcAddress(this->xinput_lib, "XInputSetState"));
if (!XInputSetState_addr) {
log_warning("xinput", "failed to get XInputSetState address");
this->stop();
return;
}
initialized = true;
log_info("xinput", "initialized");
}
XInputManager::~XInputManager() {
this->stop();
}
void XInputManager::stop() {
this->initialized = false;
if (this->xinput_lib) {
FreeLibrary(this->xinput_lib);
this->xinput_lib = nullptr;
}
XInputGetState_addr = nullptr;
XInputSetState_addr = nullptr;
log_info("xinput", "destroyed");
}
std::vector<uint8_t> XInputManager::get_available_players() {
std::vector<uint8_t> players;
if (!this->initialized) {
return players;
}
for (uint8_t i = 0; i < XUSER_MAX_COUNT; i++) {
XINPUT_STATE x;
if (XInputGetState_addr(i, &x) == ERROR_SUCCESS) {
players.push_back(i);
}
}
return players;
}
void XInputManager::get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED &state) {
state = {};
state.sThumbLX = 0.5f;
state.sThumbLY = 0.5f;
state.sThumbRX = 0.5f;
state.sThumbRY = 0.5f;
if (!this->initialized) {
return;
}
XINPUT_STATE x;
if (XInputGetState_addr(player, &x) != ERROR_SUCCESS) {
return;
}
state.wButtons = x.Gamepad.wButtons;
state.bLeftTrigger = x.Gamepad.bLeftTrigger / 255.0f;
state.bRightTrigger = x.Gamepad.bRightTrigger / 255.0f;
// left stick circular dead zone
{
const auto x_raw = x.Gamepad.sThumbLX;
const auto y_raw = -x.Gamepad.sThumbLY; // flip to make [down = positive]
const float magnitude = sqrtf(x_raw * x_raw + y_raw * y_raw);
if (magnitude > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) {
const float scaled =
(magnitude - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) /
(32767.0f - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
state.sThumbLX = (x_raw / magnitude) * scaled;
state.sThumbLY = (y_raw / magnitude) * scaled;
// normalize to [0, 1]
state.sThumbLX = std::clamp((state.sThumbLX + 1.f) / 2.f, 0.f, 1.f);
state.sThumbLY = std::clamp((state.sThumbLY + 1.f) / 2.f, 0.f, 1.f);
} else {
// within deadzone
state.sThumbLX = 0.5f;
state.sThumbLY = 0.5f;
}
}
// right stick circular dead zone
{
const auto x_raw = x.Gamepad.sThumbRX;
const auto y_raw = -x.Gamepad.sThumbRY; // flip to make [down = positive]
const float magnitude = sqrtf(x_raw * x_raw + y_raw * y_raw);
if (magnitude > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) {
const float scaled =
(magnitude - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) /
(32767.0f - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
state.sThumbRX = (x_raw / magnitude) * scaled;
state.sThumbRY = (y_raw / magnitude) * scaled;
// normalize to [0, 1]
state.sThumbRX = std::clamp((state.sThumbRX + 1.f) / 2.f, 0.f, 1.f);
state.sThumbRY = std::clamp((state.sThumbRY + 1.f) / 2.f, 0.f, 1.f);
} else {
// within deadzone
state.sThumbRX = 0.5f;
state.sThumbRY = 0.5f;
}
}
}
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) {
XINPUT_GAMEPAD_STATE_NORMALIZED state;
get_state(player, state);
switch (button) {
case XInputButtonEnum::DPAD_UP:
return (state.wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0;
case XInputButtonEnum::DPAD_DOWN:
return (state.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
case XInputButtonEnum::DPAD_LEFT:
return (state.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
case XInputButtonEnum::DPAD_RIGHT:
return (state.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
case XInputButtonEnum::START:
return (state.wButtons & XINPUT_GAMEPAD_START) != 0;
case XInputButtonEnum::BACK:
return (state.wButtons & XINPUT_GAMEPAD_BACK) != 0;
case XInputButtonEnum::LEFT_STICK:
return (state.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
case XInputButtonEnum::RIGHT_STICK:
return (state.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
case XInputButtonEnum::LEFT_SHOULDER:
return (state.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
case XInputButtonEnum::RIGHT_SHOULDER:
return (state.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
case XInputButtonEnum::BUTTON_A:
return (state.wButtons & XINPUT_GAMEPAD_A) != 0;
case XInputButtonEnum::BUTTON_B:
return (state.wButtons & XINPUT_GAMEPAD_B) != 0;
case XInputButtonEnum::BUTTON_X:
return (state.wButtons & XINPUT_GAMEPAD_X) != 0;
case XInputButtonEnum::BUTTON_Y:
return (state.wButtons & XINPUT_GAMEPAD_Y) != 0;
case XInputButtonEnum::LEFT_TRIGGER:
return state.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
case XInputButtonEnum::RIGHT_TRIGGER:
return state.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
case XInputButtonEnum::LEFT_STICK_UP:
return state.sThumbLY > 0.6f;
case XInputButtonEnum::LEFT_STICK_DOWN:
return state.sThumbLY < 0.4f;
case XInputButtonEnum::LEFT_STICK_LEFT:
return state.sThumbLX < 0.4f;
case XInputButtonEnum::LEFT_STICK_RIGHT:
return state.sThumbLX > 0.6f;
case XInputButtonEnum::RIGHT_STICK_UP:
return state.sThumbRY > 0.6f;
case XInputButtonEnum::RIGHT_STICK_DOWN:
return state.sThumbRY < 0.4f;
case XInputButtonEnum::RIGHT_STICK_LEFT:
return state.sThumbRX < 0.4f;
case XInputButtonEnum::RIGHT_STICK_RIGHT:
return state.sThumbRX > 0.6f;
default:
break;
}
return false;
}
float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) {
XINPUT_GAMEPAD_STATE_NORMALIZED state;
get_state(player, state);
switch (analog) {
case XInputAnalogEnum::LEFT_TRIGGER:
return state.bLeftTrigger;
case XInputAnalogEnum::RIGHT_TRIGGER:
return state.bRightTrigger;
case XInputAnalogEnum::LEFT_STICK_X:
return state.sThumbLX;
case XInputAnalogEnum::LEFT_STICK_Y:
return state.sThumbLY;
case XInputAnalogEnum::RIGHT_STICK_X:
return state.sThumbRX;
case XInputAnalogEnum::RIGHT_STICK_Y:
return state.sThumbRY;
default:
break;
}
return 0.5f;
}
bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) {
for (uint8_t player = 0; player < XUSER_MAX_COUNT; player++) {
for (uint16_t b = 0; b < static_cast<uint16_t>(XInputButtonEnum::COUNT); b++) {
if (is_button_pressed(player, static_cast<XInputButtonEnum>(b))) {
button.player = player;
button.button = static_cast<XInputButtonEnum>(b);
return true;
}
}
}
return false;
}
void XInputManager::set_output_state(uint8_t player, XInputOutputEnum output, float value) {
if (!this->initialized) {
return;
}
if (player >= XUSER_MAX_COUNT) {
return;
}
XINPUT_VIBRATION vibration = {};
if (output == XInputOutputEnum::LEFT_RUMBLE) {
vibration.wLeftMotorSpeed = static_cast<uint16_t>(value * 65535);
} else if (output == XInputOutputEnum::RIGHT_RUMBLE) {
vibration.wRightMotorSpeed = static_cast<uint16_t>(value * 65535);
}
XInputSetState_addr(player, &vibration);
}
#endif
}
+110
View File
@@ -0,0 +1,110 @@
#pragma once
#include <cstdint>
#include <array>
#include <vector>
#include <string>
#include <windows.h>
namespace xinput {
#define XUSER_MAX_COUNT 4
struct XINPUT_GAMEPAD_STATE {
uint16_t wButtons;
uint8_t bLeftTrigger;
uint8_t bRightTrigger;
int16_t sThumbLX;
int16_t sThumbLY;
int16_t sThumbRX;
int16_t sThumbRY;
};
struct XINPUT_GAMEPAD_STATE_NORMALIZED {
uint16_t wButtons;
float bLeftTrigger;
float bRightTrigger;
float sThumbLX;
float sThumbLY;
float sThumbRX;
float sThumbRY;
};
enum class XInputButtonEnum : uint16_t {
// actual buttons
DPAD_UP,
DPAD_DOWN,
DPAD_LEFT,
DPAD_RIGHT,
START,
BACK,
LEFT_STICK,
RIGHT_STICK,
LEFT_SHOULDER,
RIGHT_SHOULDER,
BUTTON_A,
BUTTON_B,
BUTTON_X,
BUTTON_Y,
// analog values that can be used as buttons
LEFT_TRIGGER,
RIGHT_TRIGGER,
LEFT_STICK_UP,
LEFT_STICK_DOWN,
LEFT_STICK_LEFT,
LEFT_STICK_RIGHT,
RIGHT_STICK_UP,
RIGHT_STICK_DOWN,
RIGHT_STICK_LEFT,
RIGHT_STICK_RIGHT,
COUNT
};
enum class XInputAnalogEnum {
// analog values
LEFT_TRIGGER,
RIGHT_TRIGGER,
LEFT_STICK_X,
LEFT_STICK_Y,
RIGHT_STICK_X,
RIGHT_STICK_Y,
COUNT
};
enum class XInputOutputEnum : uint16_t {
LEFT_RUMBLE,
RIGHT_RUMBLE,
COUNT
};
struct XINPUT_NEW_BUTTON {
uint8_t player;
XInputButtonEnum button;
};
std::string get_button_string(XInputButtonEnum button);
std::string get_analog_string(XInputAnalogEnum analog);
std::string get_output_string(XInputOutputEnum output);
std::string get_device_desc(uint8_t player);
class XInputManager {
private:
bool initialized = false;
HMODULE xinput_lib = nullptr;
void get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED &state);
public:
XInputManager();
~XInputManager();
void stop();
std::vector<uint8_t> get_available_players();
bool is_button_pressed(uint8_t player, XInputButtonEnum button);
float get_analog_state(uint8_t player, XInputAnalogEnum analog);
bool get_any_button_pressed(XINPUT_NEW_BUTTON &button);
void set_output_state(uint8_t player, XInputOutputEnum output, float value);
};
}
+1
View File
@@ -243,6 +243,7 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
const auto is_windowed_sub = const auto is_windowed_sub =
(GRAPHICS_IIDX_WSUB && hWnd == TDJ_SUBSCREEN_WINDOW) || (GRAPHICS_IIDX_WSUB && hWnd == TDJ_SUBSCREEN_WINDOW) ||
(hWnd == SDVX_SUBSCREEN_WINDOW) || (hWnd == SDVX_SUBSCREEN_WINDOW) ||
(hWnd == POPN_SUBSCREEN_WINDOW) ||
(hWnd == GFDM_SUBSCREEN_WINDOW); (hWnd == GFDM_SUBSCREEN_WINDOW);
if (msg == WM_CLOSE) { if (msg == WM_CLOSE) {
+14 -12
View File
@@ -219,20 +219,21 @@ intptr_t replace_pattern(HMODULE module, const std::string &signature,
strreplace(pattern_str, "??", "00"); strreplace(pattern_str, "??", "00");
auto pattern_bin = std::make_unique<uint8_t[]>(signature.length() / 2); auto pattern_bin = std::make_unique<uint8_t[]>(signature.length() / 2);
if (!hex2bin(pattern_str.c_str(), pattern_bin.get())) { if (!hex2bin(pattern_str.c_str(), pattern_bin.get())) {
return false; return 0;
} }
// build signature mask // build signature mask
std::ostringstream signature_mask; std::string signature_mask;
signature_mask.reserve(signature.size() / 2);
for (size_t i = 0; i < signature.length(); i += 2) { for (size_t i = 0; i < signature.length(); i += 2) {
if (signature[i] == '?') { if (signature[i] == '?') {
if (signature[i + 1] == '?') { if (signature[i + 1] == '?') {
signature_mask << '?'; signature_mask += '?';
} else { } else {
return false; return 0;
} }
} else { } else {
signature_mask << 'X'; signature_mask += 'X';
} }
} }
@@ -241,20 +242,21 @@ intptr_t replace_pattern(HMODULE module, const std::string &signature,
strreplace(replace_data_str, "??", "00"); strreplace(replace_data_str, "??", "00");
auto replace_data_bin = std::make_unique<uint8_t[]>(replacement.length() / 2); auto replace_data_bin = std::make_unique<uint8_t[]>(replacement.length() / 2);
if (!hex2bin(replace_data_str.c_str(), replace_data_bin.get())) { if (!hex2bin(replace_data_str.c_str(), replace_data_bin.get())) {
return false; return 0;
} }
// build replace mask // build replace mask
std::ostringstream replace_mask; std::string replace_mask;
replace_mask.reserve(replacement.size() / 2);
for (size_t i = 0; i < replacement.length(); i += 2) { for (size_t i = 0; i < replacement.length(); i += 2) {
if (replacement[i] == '?') { if (replacement[i] == '?') {
if (replacement[i + 1] == '?') { if (replacement[i + 1] == '?') {
replace_mask << '?'; replace_mask += '?';
} else { } else {
return false; return 0;
} }
} else { } else {
replace_mask << 'X'; replace_mask += 'X';
} }
} }
@@ -262,11 +264,11 @@ intptr_t replace_pattern(HMODULE module, const std::string &signature,
return replace_pattern( return replace_pattern(
module, module,
pattern_bin.get(), pattern_bin.get(),
signature_mask.str().c_str(), signature_mask.c_str(),
offset, offset,
usage, usage,
replace_data_bin.get(), replace_data_bin.get(),
replace_mask.str().c_str() replace_mask.c_str()
); );
} }
+208 -29
View File
@@ -1,3 +1,6 @@
// GetDisplayConfigBufferSizes etc is Vista+
#define _WIN32_WINNT 0x0601
#include "sysutils.h" #include "sysutils.h"
#include <cstdlib> #include <cstdlib>
@@ -7,6 +10,10 @@
#include <windows.h> #include <windows.h>
#undef WIN32_NO_STATUS #undef WIN32_NO_STATUS
#include "hooks/graphics/graphics.h"
#include "avs/game.h"
#include "util/detour.h"
#include "util/flags_helper.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h" #include "util/utils.h"
@@ -20,7 +27,7 @@
namespace sysutils { namespace sysutils {
#pragma pack(push) #pragma pack(push)
#pragma pack(1) #pragma pack(1)
// from https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemfirmwaretable // from https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemfirmwaretable
@@ -83,6 +90,9 @@ namespace sysutils {
); );
static GetSystemFirmwareTable_t GetSystemFirmwareTable = nullptr; static GetSystemFirmwareTable_t GetSystemFirmwareTable = nullptr;
std::string SECOND_MONITOR_OVERRIDE = "";
static decltype(EnumDisplayDevicesA) *EnumDisplayDevicesA_orig = nullptr;
void print_smbios() { void print_smbios() {
DWORD bytes_written = 0; DWORD bytes_written = 0;
DWORD table_size = 0; DWORD table_size = 0;
@@ -209,58 +219,87 @@ namespace sysutils {
return strings; return strings;
} }
static std::string device_flags_to_string(DWORD flags) {
FLAGS_START(flags);
FLAG(flags, DISPLAY_DEVICE_ATTACHED_TO_DESKTOP);
FLAG(flags, DISPLAY_DEVICE_MULTI_DRIVER);
FLAG(flags, DISPLAY_DEVICE_PRIMARY_DEVICE);
FLAG(flags, DISPLAY_DEVICE_MIRRORING_DRIVER);
FLAG(flags, DISPLAY_DEVICE_VGA_COMPATIBLE);
FLAG(flags, DISPLAY_DEVICE_REMOVABLE);
#if !defined(DISPLAY_DEVICE_ACC_DRIVER)
#define DISPLAY_DEVICE_ACC_DRIVER 0x00000040
#endif
FLAG(flags, DISPLAY_DEVICE_ACC_DRIVER);
FLAG(flags, DISPLAY_DEVICE_MODESPRUNED);
FLAG(flags, DISPLAY_DEVICE_RDPUDD);
FLAG(flags, DISPLAY_DEVICE_REMOTE);
FLAG(flags, DISPLAY_DEVICE_DISCONNECT);
FLAG(flags, DISPLAY_DEVICE_TS_COMPATIBLE);
#if !defined(DISPLAY_DEVICE_UNSAFE_MODES_ON)
#define DISPLAY_DEVICE_UNSAFE_MODES_ON 0x00080000
#endif
FLAG(flags, DISPLAY_DEVICE_UNSAFE_MODES_ON);
FLAGS_END(flags);
}
static std::string monitor_flags_to_string(DWORD flags) {
FLAGS_START(flags);
FLAG(flags, DISPLAY_DEVICE_ACTIVE);
FLAG(flags, DISPLAY_DEVICE_ATTACHED);
FLAGS_END(flags);
}
static void print_adapter(DWORD index, PDISPLAY_DEVICEA adapter, bool is_monitor) { static void print_adapter(DWORD index, PDISPLAY_DEVICEA adapter, bool is_monitor) {
if (adapter->StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) { if (!is_monitor) {
return; log_misc("gpuinfo", "-------- device {} --------", index);
}
if (!(adapter->StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) {
return;
} }
std::string prefix("device"); std::string prefix("device");
if (is_monitor) { if (is_monitor) {
prefix = " adapter"; prefix = " adapter";
} }
log_misc("gpuinfo", "{} {} device name : {}", prefix.c_str(), index, adapter->DeviceName);
log_misc("gpuinfo", "{} {} device string : {}", prefix.c_str(), index, adapter->DeviceString);
log_dbug("gpuinfo", "{} {} flags : 0x{:x}", prefix.c_str(), index, adapter->StateFlags);
if (!is_monitor) { if (!is_monitor) {
// get extended info for better friendly name of monitors // get extended info for better friendly name of monitors
std::string friendly_name = "unknown";
const auto &monitors = enumerate_monitors(); const auto &monitors = enumerate_monitors();
for (const auto& monitor : monitors) { for (const auto& monitor : monitors) {
if (monitor.display_name == adapter->DeviceName) { if (monitor.display_name == adapter->DeviceName) {
const auto friendly = ws2s(monitor.friendly_name.c_str()); friendly_name = ws2s(monitor.friendly_name.c_str());
log_misc(
"gpuinfo", "{} {} friendly name : {}",
prefix.c_str(),
index,
friendly);
break; break;
} }
} }
log_misc("gpuinfo", "{} {} name : {} ({}) @ {}",
prefix.c_str(), index,
adapter->DeviceName, friendly_name, adapter->DeviceString);
// resolution, refresh rate // resolution, refresh rate
DEVMODEA devmode = {}; DEVMODEA devmode = {};
devmode.dmSize = sizeof(devmode); devmode.dmSize = sizeof(devmode);
if (EnumDisplaySettingsA(adapter->DeviceName, ENUM_CURRENT_SETTINGS, &devmode)) { if (EnumDisplaySettingsA(adapter->DeviceName, ENUM_CURRENT_SETTINGS, &devmode)) {
log_misc( log_misc(
"gpuinfo", "gpuinfo", "{} {} resolution : {}px * {}px @ {}Hz",
"{} {} resolution : {}px * {}px @ {}Hz",
prefix.c_str(), prefix.c_str(),
index, index,
devmode.dmPelsWidth, devmode.dmPelsHeight, devmode.dmPelsWidth, devmode.dmPelsHeight,
devmode.dmDisplayFrequency); devmode.dmDisplayFrequency);
} else {
log_misc("gpuinfo", "EnumDisplaySettingsA failed");
} }
log_misc("gpuinfo", "{} {} flags : {}",
prefix.c_str(), index, device_flags_to_string(adapter->StateFlags));
// primary? } else {
log_misc( log_misc("gpuinfo", "{} {} name : {} ({})",
"gpuinfo", "{} {} is primary : {}", prefix.c_str(), index,
prefix.c_str(), adapter->DeviceName, adapter->DeviceString);
index,
(adapter->StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) ? "yes" : "no");
log_misc("gpuinfo", "{} {} flags : {}",
prefix.c_str(), index, monitor_flags_to_string(adapter->StateFlags));
} }
} }
@@ -318,7 +357,7 @@ namespace sysutils {
return; return;
} }
} }
static std::vector<MonitorEntry> enumerate_monitors_internal() { static std::vector<MonitorEntry> enumerate_monitors_internal() {
// for WinXP, since these are Vista+ or 7+ APIs // for WinXP, since these are Vista+ or 7+ APIs
const auto user32 = LoadLibraryA("user32.dll"); const auto user32 = LoadLibraryA("user32.dll");
@@ -370,9 +409,28 @@ namespace sysutils {
std::vector<MonitorEntry> result; std::vector<MonitorEntry> result;
for (const auto& path : paths) { for (const auto& path : paths) {
MonitorEntry entry; MonitorEntry entry = {};
entry.adapter_id_LowPart = path.targetInfo.adapterId.LowPart;
entry.adapter_id_HighPart = path.targetInfo.adapterId.HighPart;
entry.id = path.targetInfo.id;
// device ID (\\.\DISPLAYn) // primary monitor?
for (const auto& mode : modes) {
if (mode.infoType != DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE) {
continue;
}
if (mode.adapterId.HighPart == path.sourceInfo.adapterId.HighPart &&
mode.adapterId.LowPart == path.sourceInfo.adapterId.LowPart &&
mode.id == path.sourceInfo.id) {
if (mode.sourceMode.position.x == 0 && mode.sourceMode.position.y == 0) {
entry.is_primary = true;
}
break;
}
}
// device ID (\\.\DISPLAYn)
DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name = {}; DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name = {};
source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
source_name.header.size = sizeof(source_name); source_name.header.size = sizeof(source_name);
@@ -406,4 +464,125 @@ namespace sysutils {
static const std::vector<MonitorEntry> monitors = enumerate_monitors_internal(); static const std::vector<MonitorEntry> monitors = enumerate_monitors_internal();
return monitors; return monitors;
} }
static
BOOL
__stdcall
EnumDisplayDevicesA_hook(
LPCSTR lpDevice,
DWORD iDevNum,
PDISPLAY_DEVICEA lpDisplayDevice,
DWORD dwFlags
) {
if (EnumDisplayDevicesA_orig == nullptr) {
return false;
}
// caller is enumerating monitors (adapters), not devices
if (lpDevice != nullptr) {
log_misc("sysutils", "EnumDisplayDevicesA: returning original results for device {} [{}]", lpDevice, iDevNum);
return EnumDisplayDevicesA_orig(lpDevice, iDevNum, lpDisplayDevice, dwFlags);
}
// call the original first
const auto result_orig = EnumDisplayDevicesA_orig(nullptr, iDevNum, lpDisplayDevice, dwFlags);
if (!result_orig) {
return result_orig;
}
log_misc(
"sysutils",
"EnumDisplayDevicesA_orig: {} [{}], StateFlags={:#x}",
lpDisplayDevice->DeviceName,
iDevNum,
lpDisplayDevice->StateFlags);
// this one is not relevant
if ((lpDisplayDevice->StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) {
return result_orig;
}
// if this is for the primary device, just return it
if (lpDisplayDevice->StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
log_misc(
"sysutils",
"EnumDisplayDevicesA: returning original results for primary monitor [{}], {}, result={}",
iDevNum,
lpDisplayDevice->DeviceName,
result_orig);
return result_orig;
}
// this is not 100% confirmed, but hope that this helps in cases where second monitors get used
// even though NumberOfAdaptersInGroup was set to 1 on hybrid laptops
if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
lpDisplayDevice->StateFlags &= ~DISPLAY_DEVICE_ATTACHED_TO_DESKTOP;
log_info(
"sysutils",
"hiding this monitor, {} @ index {} (-graphics-single-adapter)",
lpDisplayDevice->DeviceName,
iDevNum);
return result_orig;
}
// for the second device (subscreen)...
if (SECOND_MONITOR_OVERRIDE.empty()) {
// if there is no user override, we find the device with lowest index (that isn't primary)
for (DWORD i = 0;; i++) {
DISPLAY_DEVICEA device = {};
device.cb = sizeof(device);
const auto result = EnumDisplayDevicesA_orig(nullptr, i, &device, 0);
if (!result) {
break;
}
if (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
// primary, skip this
continue;
}
if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) {
// disconnected, skip this as well
continue;
}
if (std::string(lpDisplayDevice->DeviceName) == device.DeviceName) {
log_info(
"sysutils",
"returning second monitor {} @ index {}",
lpDisplayDevice->DeviceName,
iDevNum);
return result_orig;
} else {
// otherwise, fall through and hide this monitor
break;
}
}
} else if (SECOND_MONITOR_OVERRIDE == lpDisplayDevice->DeviceName) {
// if there is one preferred by the user, use this one, and hide others
log_info(
"sysutils",
"returning second monitor, {} @ index {} (-iidxsubmonitor / -sdvxsubmonitor)",
lpDisplayDevice->DeviceName,
iDevNum);
return result_orig;
}
// this device should not be used; pretend that it's not connected
log_info(
"sysutils",
"hiding this monitor, {} @ index {}",
lpDisplayDevice->DeviceName,
iDevNum);
lpDisplayDevice->StateFlags &= ~DISPLAY_DEVICE_ATTACHED_TO_DESKTOP;
return result_orig;
}
void hook_EnumDisplayDevicesA() {
// the goal is to trick DX9 into thinking that some monitors are not attached
// so we need to trampoline hook at user32 instead of IAT at game level
log_misc("sysutils", "hooking EnumDisplayDevicesA to hide if there are more than 3 monitors...");
detour::trampoline_try(
"user32.dll",
"EnumDisplayDevicesA",
(void*)EnumDisplayDevicesA_hook,
(void**)&EnumDisplayDevicesA_orig);
}
} }
+7
View File
@@ -12,7 +12,14 @@ namespace sysutils {
struct MonitorEntry { struct MonitorEntry {
std::string display_name; // \\.\DISPLAY1 std::string display_name; // \\.\DISPLAY1
std::wstring friendly_name; // Dell S2204T; wstring so that it can be converted to utf8 or string std::wstring friendly_name; // Dell S2204T; wstring so that it can be converted to utf8 or string
bool is_primary;
uint32_t adapter_id_LowPart;
int32_t adapter_id_HighPart;
uint32_t id;
}; };
const std::vector<MonitorEntry> &enumerate_monitors(); const std::vector<MonitorEntry> &enumerate_monitors();
extern std::string SECOND_MONITOR_OVERRIDE;
void hook_EnumDisplayDevicesA();
} }
+12 -3
View File
@@ -9,7 +9,7 @@ namespace tapeledutils {
} }
// for bi2x-style byte array of all colors and LEDs at once // for bi2x-style byte array of all colors and LEDs at once
rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size) { static rgb_float3_t pick_color_from_led_tape_internal(uint8_t *data, size_t data_size, uint8_t divisor) {
rgb_float3_t result = {0.f, 0.f, 0.f}; rgb_float3_t result = {0.f, 0.f, 0.f};
if (TAPE_LED_ALGORITHM == TAPE_LED_USE_AVERAGE) { if (TAPE_LED_ALGORITHM == TAPE_LED_USE_AVERAGE) {
@@ -25,7 +25,7 @@ namespace tapeledutils {
} }
// normalize // normalize
const float avg_mult = 1.f / (data_size * 255); const float avg_mult = 1.f / (data_size * divisor);
result.r = avg_ri * avg_mult; result.r = avg_ri * avg_mult;
result.g = avg_gi * avg_mult; result.g = avg_gi * avg_mult;
result.b = avg_bi * avg_mult; result.b = avg_bi * avg_mult;
@@ -50,7 +50,7 @@ namespace tapeledutils {
} }
// normalize // normalize
const float single_mult = 1.f / 255; const float single_mult = 1.f / divisor;
result.r = color[0] * single_mult; result.r = color[0] * single_mult;
result.g = color[1] * single_mult; result.g = color[1] * single_mult;
result.b = color[2] * single_mult; result.b = color[2] * single_mult;
@@ -58,6 +58,15 @@ namespace tapeledutils {
return result; return result;
} }
rgb_float3_t pick_color_from_led_tape(tape_led &light, uint8_t *data, size_t data_size) {
const auto max_value = (light.max_value > 0) ? light.max_value : 0xFF;
return pick_color_from_led_tape_internal(data, data_size, max_value);
}
rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size) {
return pick_color_from_led_tape_internal(data, data_size, 0xFF);
}
// for bi2a-style that calls for each individual LED // for bi2a-style that calls for each individual LED
size_t get_led_index_using_avg_algo(size_t data_size) { size_t get_led_index_using_avg_algo(size_t data_size) {
size_t index_to_use; size_t index_to_use;
+22 -2
View File
@@ -25,13 +25,33 @@ namespace tapeledutils {
struct tape_led { struct tape_led {
std::vector<rgb_float3_t> data; std::vector<rgb_float3_t> data;
int index_r, index_g, index_b; // Averaged RGB light output indexes int index_r, index_g, index_b; // Averaged RGB light output indexes
uint8_t max_value;
std::string lightName; std::string lightName;
tape_led(size_t data_size, int index_r, int index_g, int index_b, std::string lightName) tape_led(
: data(std::vector<rgb_float3_t>(data_size)), index_r(index_r), index_g(index_g), index_b(index_b), lightName(std::move(lightName)) {} size_t data_size,
int index_r, int index_g, int index_b,
std::string lightName)
: data(
std::vector<rgb_float3_t>(data_size)),
index_r(index_r), index_g(index_g), index_b(index_b),
max_value(0),
lightName(std::move(lightName)) {}
tape_led(
size_t data_size,
int index_r, int index_g, int index_b,
uint8_t max_value,
std::string lightName)
: data(
std::vector<rgb_float3_t>(data_size)),
index_r(index_r), index_g(index_g), index_b(index_b),
max_value(max_value),
lightName(std::move(lightName)) {}
}; };
bool is_enabled(); bool is_enabled();
rgb_float3_t pick_color_from_led_tape(tape_led &light, uint8_t *data, size_t data_size);
rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size); rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size);
size_t get_led_index_using_avg_algo(size_t data_size); size_t get_led_index_using_avg_algo(size_t data_size);
} }
+1 -1
View File
@@ -38,7 +38,7 @@ namespace unity_utils {
// monitor // monitor
if (D3D9_ADAPTER.has_value()) { if (D3D9_ADAPTER.has_value()) {
args += fmt::format(" -monitor {}", D3D9_ADAPTER.value()); args += fmt::format(" -dxmainadapter {}", D3D9_ADAPTER.value());
} }
return args; return args;