mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-09-30 14:04:55 -07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0169fe1e31 | |||
| 357e6c86f1 | |||
| 67963389ef |
@@ -520,6 +520,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
games/scotto/io.cpp
|
games/scotto/io.cpp
|
||||||
games/drs/drs.cpp
|
games/drs/drs.cpp
|
||||||
games/drs/io.cpp
|
games/drs/io.cpp
|
||||||
|
games/drs/motion_cam.cpp
|
||||||
games/drs/rgb_cam.cpp
|
games/drs/rgb_cam.cpp
|
||||||
games/we/we.cpp
|
games/we/we.cpp
|
||||||
games/we/io.cpp
|
games/we/io.cpp
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/precise_timer.h"
|
#include "util/precise_timer.h"
|
||||||
#include "util/memutils.h"
|
#include "util/memutils.h"
|
||||||
|
#include "io.h"
|
||||||
|
#include "motion_cam.h"
|
||||||
#include "rgb_cam.h"
|
#include "rgb_cam.h"
|
||||||
|
|
||||||
#pragma pack(push)
|
#pragma pack(push)
|
||||||
@@ -276,48 +278,50 @@ namespace games::drs {
|
|||||||
void DRSGame::attach() {
|
void DRSGame::attach() {
|
||||||
Game::attach();
|
Game::attach();
|
||||||
|
|
||||||
// TouchSDK hooks
|
|
||||||
detour::iat("??0TouchSDK@@QEAA@XZ",
|
|
||||||
(void *) &TouchSDK_Constructor, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?SendData@TouchSDK@@QEAA_NU_DeviceInfo@@QEAEH1HH@Z",
|
|
||||||
(void *) &TouchSDK_SendData, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?SetSignalInit@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
|
||||||
(void *) &TouchSDK_SetSignalInit, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("??1TouchSDK@@QEAA@XZ",
|
|
||||||
(void *) &TouchSDK_Destructor, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?GetYLedTotal@TouchSDK@@QEAAHU_DeviceInfo@@H@Z",
|
|
||||||
(void *) &TouchSDK_GetYLedTotal, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?GetXLedTotal@TouchSDK@@QEAAHU_DeviceInfo@@H@Z",
|
|
||||||
(void *) &TouchSDK_GetXLedTotal, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?DisableTouch@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
|
||||||
(void *) &TouchSDK_DisableTouch, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?DisableDrag@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
|
||||||
(void *) &TouchSDK_DisableDrag, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?DisableWheel@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
|
||||||
(void *) &TouchSDK_DisableWheel, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?DisableRightClick@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
|
||||||
(void *) &TouchSDK_DisableRightClick, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?SetMultiTouchMode@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
|
||||||
(void *) &TouchSDK_SetMultiTouchMode, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?EnableTouchWidthData@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
|
||||||
(void *) &TouchSDK_EnableTouchWidthData, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?EnableRawData@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
|
||||||
(void *) &TouchSDK_EnableRawData, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?SetAllEnable@TouchSDK@@QEAA_NU_DeviceInfo@@_NH@Z",
|
|
||||||
(void *) &TouchSDK_SetAllEnable, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?GetTouchDeviceCount@TouchSDK@@QEAAHXZ",
|
|
||||||
(void *) &TouchSDK_GetTouchDeviceCount, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?GetTouchSDKVersion@TouchSDK@@QEAAIXZ",
|
|
||||||
(void *) &TouchSDK_GetTouchSDKVersion, avs::game::DLL_INSTANCE);
|
|
||||||
detour::iat("?InitTouch@TouchSDK@@QEAAHPEAU_DeviceInfo@@HP6AXU2@PEBU_TouchPointData@@HHPEBX@ZP6AX1_N3@ZPEAX@Z",
|
|
||||||
(void *) &TouchSDK_InitTouch, avs::game::DLL_INSTANCE);
|
|
||||||
|
|
||||||
if (!DISABLE_TOUCH) {
|
if (!DISABLE_TOUCH) {
|
||||||
|
// TouchSDK hooks
|
||||||
|
detour::iat("??0TouchSDK@@QEAA@XZ",
|
||||||
|
(void *) &TouchSDK_Constructor, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?SendData@TouchSDK@@QEAA_NU_DeviceInfo@@QEAEH1HH@Z",
|
||||||
|
(void *) &TouchSDK_SendData, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?SetSignalInit@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
||||||
|
(void *) &TouchSDK_SetSignalInit, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("??1TouchSDK@@QEAA@XZ",
|
||||||
|
(void *) &TouchSDK_Destructor, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?GetYLedTotal@TouchSDK@@QEAAHU_DeviceInfo@@H@Z",
|
||||||
|
(void *) &TouchSDK_GetYLedTotal, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?GetXLedTotal@TouchSDK@@QEAAHU_DeviceInfo@@H@Z",
|
||||||
|
(void *) &TouchSDK_GetXLedTotal, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?DisableTouch@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
||||||
|
(void *) &TouchSDK_DisableTouch, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?DisableDrag@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
||||||
|
(void *) &TouchSDK_DisableDrag, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?DisableWheel@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
||||||
|
(void *) &TouchSDK_DisableWheel, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?DisableRightClick@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
||||||
|
(void *) &TouchSDK_DisableRightClick, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?SetMultiTouchMode@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
||||||
|
(void *) &TouchSDK_SetMultiTouchMode, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?EnableTouchWidthData@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
||||||
|
(void *) &TouchSDK_EnableTouchWidthData, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?EnableRawData@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
|
||||||
|
(void *) &TouchSDK_EnableRawData, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?SetAllEnable@TouchSDK@@QEAA_NU_DeviceInfo@@_NH@Z",
|
||||||
|
(void *) &TouchSDK_SetAllEnable, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?GetTouchDeviceCount@TouchSDK@@QEAAHXZ",
|
||||||
|
(void *) &TouchSDK_GetTouchDeviceCount, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?GetTouchSDKVersion@TouchSDK@@QEAAIXZ",
|
||||||
|
(void *) &TouchSDK_GetTouchSDKVersion, avs::game::DLL_INSTANCE);
|
||||||
|
detour::iat("?InitTouch@TouchSDK@@QEAAHPEAU_DeviceInfo@@HP6AXU2@PEBU_TouchPointData@@HHPEBX@ZP6AX1_N3@ZPEAX@Z",
|
||||||
|
(void *) &TouchSDK_InitTouch, avs::game::DLL_INSTANCE);
|
||||||
|
|
||||||
start_touch();
|
start_touch();
|
||||||
} else {
|
} else {
|
||||||
log_info("drs", "no native input method detected");
|
log_info("drs", "touch input for dance floor disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init_down_motion_hook();
|
||||||
|
|
||||||
if (RGB_CAMERA_HOOK) {
|
if (RGB_CAMERA_HOOK) {
|
||||||
init_rgb_camera_hook();
|
init_rgb_camera_hook();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ std::vector<Button> &games::drs::get_buttons() {
|
|||||||
"P2 Up",
|
"P2 Up",
|
||||||
"P2 Down",
|
"P2 Down",
|
||||||
"P2 Left",
|
"P2 Left",
|
||||||
"P2 Right"
|
"P2 Right",
|
||||||
|
"Down Motion"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,8 +31,8 @@ std::vector<Button> &games::drs::get_buttons() {
|
|||||||
std::string games::drs::get_buttons_help() {
|
std::string games::drs::get_buttons_help() {
|
||||||
// keep to max 100 characters wide
|
// keep to max 100 characters wide
|
||||||
return
|
return
|
||||||
"Motion camera required for DOWN movement.\n"
|
"Touchscreen supported for dance floor.\n"
|
||||||
"Touchscreen supported for dance floor."
|
"Down Motion applies to both players."
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ namespace games::drs {
|
|||||||
P2_Up,
|
P2_Up,
|
||||||
P2_Down,
|
P2_Down,
|
||||||
P2_Left,
|
P2_Left,
|
||||||
P2_Right
|
P2_Right,
|
||||||
|
DownMotion
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
#include "games/drs/motion_cam.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "avs/game.h"
|
||||||
|
#include "util/detour.h"
|
||||||
|
#include "util/logging.h"
|
||||||
|
#include "util/sigscan.h"
|
||||||
|
#include "io.h"
|
||||||
|
|
||||||
|
namespace games::drs {
|
||||||
|
|
||||||
|
// the game reads down movement from a depth camera whose driver is compiled into the game DLL,
|
||||||
|
// so there is no import to replace.
|
||||||
|
//
|
||||||
|
// with no camera the tracked height never changes, so the motion state never leaves "still"
|
||||||
|
// and DOWN notes can never be hit.
|
||||||
|
//
|
||||||
|
// rather than answer CInputManager::IsDown, the button drives the same state pair the camera
|
||||||
|
// would; that leaves the game's own edge test untouched and keeps every note in a frame seeing
|
||||||
|
// one answer.
|
||||||
|
static int32_t *MOTION_STATE = nullptr;
|
||||||
|
static int32_t *MOTION_STATE_PREV = nullptr;
|
||||||
|
static constexpr int32_t MOTION_STATE_STILL = 1;
|
||||||
|
static constexpr int32_t MOTION_STATE_DOWN = 2;
|
||||||
|
|
||||||
|
static void (*UpdateMotion_orig)() = nullptr;
|
||||||
|
static bool DOWN_MOTION_HELD = false;
|
||||||
|
|
||||||
|
static void InputManager_UpdateMotion() {
|
||||||
|
UpdateMotion_orig();
|
||||||
|
|
||||||
|
auto &buttons = get_buttons();
|
||||||
|
const bool held = GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::DownMotion));
|
||||||
|
|
||||||
|
// a state of DOWN only counts as movement when the frame before it was something else
|
||||||
|
if (held) {
|
||||||
|
*MOTION_STATE_PREV = DOWN_MOTION_HELD ? MOTION_STATE_DOWN : MOTION_STATE_STILL;
|
||||||
|
*MOTION_STATE = MOTION_STATE_DOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
DOWN_MOTION_HELD = held;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_down_motion_hook() {
|
||||||
|
// only hook motion camera if the user has down motion button bound to something
|
||||||
|
auto &buttons = get_buttons();
|
||||||
|
if (!buttons.at(Buttons::DownMotion).isSet()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CInputManager::IsDown - read the state pair out of its two operands.
|
||||||
|
// Identical in game versions 2020121400 and 2022121400 / 2024120300:
|
||||||
|
//
|
||||||
|
// 83 3D xx xx xx xx 02 cmp dword [rip+prev], 2 ; state one frame ago
|
||||||
|
// 74 0C je FALSE ; already down, so no new edge
|
||||||
|
// 83 3D xx xx xx xx 02 cmp dword [rip+cur], 2 ; state this frame
|
||||||
|
// 75 03 jne FALSE ; not down
|
||||||
|
// B0 01 mov al, 1 ; went down on this frame
|
||||||
|
// C3 ret
|
||||||
|
// 32 C0 FALSE: xor al, al
|
||||||
|
// C3 ret
|
||||||
|
auto is_down = reinterpret_cast<uint8_t *>(find_pattern(
|
||||||
|
avs::game::DLL_INSTANCE,
|
||||||
|
"833D0000000002740C833D00000000027503B001C332C0C3",
|
||||||
|
"XX????XXXXX????XXXXXXXXX",
|
||||||
|
0, 0));
|
||||||
|
|
||||||
|
if (is_down == nullptr) {
|
||||||
|
log_warning("drs", "motion sensor state not found, DOWN motion button unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rip-relative displacements resolve against the end of their own instruction, and each
|
||||||
|
// cmp above is 7 bytes with its displacement 4 bytes in: the first ends at +7 with its
|
||||||
|
// displacement at +2, the second starts at +9 so it ends at +16 with its own at +11.
|
||||||
|
// read signed, since a target sitting below the instruction encodes as negative.
|
||||||
|
MOTION_STATE_PREV = (int32_t *) (is_down + 7 + *(int32_t *) (is_down + 2));
|
||||||
|
MOTION_STATE = (int32_t *) (is_down + 16 + *(int32_t *) (is_down + 11));
|
||||||
|
|
||||||
|
// CInputManager::UpdateMotion is hooked for the sole purpose of reading the Down Motion
|
||||||
|
// button mapping frequently enough so that the rising edge can be detected.
|
||||||
|
//
|
||||||
|
// Matched over the prologue up to the camera read; the wildcards are a float and a callee:
|
||||||
|
//
|
||||||
|
// 48 83 EC 38 sub rsp, 0x38
|
||||||
|
// 0F 29 74 24 20 movaps [rsp+0x20], xmm6
|
||||||
|
// F3 0F 10 35 xx xx xx xx movss xmm6, [rip+height] ; reading as of last frame
|
||||||
|
// E8 xx xx xx xx call GetPlayVideoProcess
|
||||||
|
// 48 8B C8 mov rcx, rax
|
||||||
|
// 48 8B 10 mov rdx, [rax]
|
||||||
|
// FF 52 60 call [rdx+0x60] ; reading off the newest frame
|
||||||
|
auto update = reinterpret_cast<void *>(find_pattern(
|
||||||
|
avs::game::DLL_INSTANCE,
|
||||||
|
"4883EC380F29742420F30F103500000000E800000000488BC8488B10FF5260",
|
||||||
|
"XXXXXXXXXXXXX????X????XXXXXXXXX",
|
||||||
|
0, 0));
|
||||||
|
|
||||||
|
if (update == nullptr ||
|
||||||
|
!detour::trampoline_try(
|
||||||
|
update, (void *) InputManager_UpdateMotion, (void **) &UpdateMotion_orig)) {
|
||||||
|
log_warning("drs", "motion update not hooked, DOWN motion button unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("drs", "hooked DOWN motion at +{:#x}",
|
||||||
|
(uintptr_t) ((uint8_t *) update - (uint8_t *) avs::game::DLL_INSTANCE));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace games::drs {
|
||||||
|
void init_down_motion_hook();
|
||||||
|
}
|
||||||
@@ -39,10 +39,38 @@ namespace games::gitadora {
|
|||||||
std::optional<socd::SocdAlgorithm> PICK_ALGO = socd::SocdAlgorithm::PreferRecent;
|
std::optional<socd::SocdAlgorithm> PICK_ALGO = socd::SocdAlgorithm::PreferRecent;
|
||||||
std::optional<uint8_t> ARENA_WINDOW_COUNT = std::nullopt;
|
std::optional<uint8_t> ARENA_WINDOW_COUNT = std::nullopt;
|
||||||
bool ARENA_TWO_HEAD_EXCLUSIVE = false;
|
bool ARENA_TWO_HEAD_EXCLUSIVE = false;
|
||||||
|
ArenaSubscreenLandscape ARENA_SUBSCREEN_LANDSCAPE = ArenaSubscreenLandscape::Off;
|
||||||
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||||||
bool ALLOW_REALTEK_AUDIO = false;
|
bool ALLOW_REALTEK_AUDIO = false;
|
||||||
bool NATIVE_TOUCH = false;
|
bool NATIVE_TOUCH = false;
|
||||||
|
|
||||||
|
std::pair<UINT, UINT> arena_subscreen_host_size() {
|
||||||
|
if (GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
|
||||||
|
return GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value();
|
||||||
|
}
|
||||||
|
if (arena_subscreen_landscape()) {
|
||||||
|
return { ARENA_SUBSCREEN_LANDSCAPE_WIDTH, ARENA_SUBSCREEN_LANDSCAPE_HEIGHT };
|
||||||
|
}
|
||||||
|
return { ARENA_SUBSCREEN_WIDTH, ARENA_SUBSCREEN_HEIGHT };
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT arena_subscreen_content_rect(LONG host_width, LONG host_height) {
|
||||||
|
if (host_width <= 0 || host_height <= 0) {
|
||||||
|
return RECT {};
|
||||||
|
}
|
||||||
|
|
||||||
|
LONG width = MulDiv(host_height, ARENA_SUBSCREEN_WIDTH, ARENA_SUBSCREEN_HEIGHT);
|
||||||
|
LONG height = host_height;
|
||||||
|
if (width > host_width) {
|
||||||
|
width = host_width;
|
||||||
|
height = MulDiv(host_width, ARENA_SUBSCREEN_HEIGHT, ARENA_SUBSCREEN_WIDTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LONG left = (host_width - width) / 2;
|
||||||
|
const LONG top = (host_height - height) / 2;
|
||||||
|
return RECT { left, top, left + width, top + height };
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prevent GitaDora from creating folders on F drive
|
* Prevent GitaDora from creating folders on F drive
|
||||||
*/
|
*/
|
||||||
@@ -343,6 +371,23 @@ namespace games::gitadora {
|
|||||||
"gitadora",
|
"gitadora",
|
||||||
"arena model: unsupported window count: {}", count);
|
"arena model: unsupported window count: {}", count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (arena_subscreen_landscape() && !ARENA_TWO_HEAD_EXCLUSIVE) {
|
||||||
|
log_warning(
|
||||||
|
"gitadora",
|
||||||
|
"arena model: landscape subscreen needs full screen two-window mode, ignoring");
|
||||||
|
ARENA_SUBSCREEN_LANDSCAPE = ArenaSubscreenLandscape::Off;
|
||||||
|
}
|
||||||
|
if (ARENA_TWO_HEAD_EXCLUSIVE) {
|
||||||
|
const auto [host_width, host_height] = arena_subscreen_host_size();
|
||||||
|
if (host_width != ARENA_SUBSCREEN_WIDTH
|
||||||
|
|| host_height != ARENA_SUBSCREEN_HEIGHT) {
|
||||||
|
log_info(
|
||||||
|
"gitadora",
|
||||||
|
"arena model: SMALL head runs at {}x{}, subscreen is scaled to fit",
|
||||||
|
host_width, host_height);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <mmreg.h>
|
#include <mmreg.h>
|
||||||
@@ -11,6 +12,14 @@
|
|||||||
|
|
||||||
namespace games::gitadora {
|
namespace games::gitadora {
|
||||||
|
|
||||||
|
// what a landscape monitor driving the SMALL head does with the space beside the
|
||||||
|
// portrait subscreen image
|
||||||
|
enum class ArenaSubscreenLandscape {
|
||||||
|
Off, // SMALL head stays portrait
|
||||||
|
Small, // subscreen centered, the space either side stays black
|
||||||
|
All, // that space shows the LEFT and RIGHT heads instead
|
||||||
|
};
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
extern bool TWOCHANNEL;
|
extern bool TWOCHANNEL;
|
||||||
extern bool DISABLE_FRAME_LIMITER;
|
extern bool DISABLE_FRAME_LIMITER;
|
||||||
@@ -21,14 +30,35 @@ namespace games::gitadora {
|
|||||||
extern std::optional<socd::SocdAlgorithm> PICK_ALGO;
|
extern std::optional<socd::SocdAlgorithm> PICK_ALGO;
|
||||||
extern std::optional<uint8_t> ARENA_WINDOW_COUNT;
|
extern std::optional<uint8_t> ARENA_WINDOW_COUNT;
|
||||||
extern bool ARENA_TWO_HEAD_EXCLUSIVE;
|
extern bool ARENA_TWO_HEAD_EXCLUSIVE;
|
||||||
|
extern ArenaSubscreenLandscape ARENA_SUBSCREEN_LANDSCAPE;
|
||||||
extern std::optional<std::string> ASIO_DRIVER;
|
extern std::optional<std::string> ASIO_DRIVER;
|
||||||
extern bool ALLOW_REALTEK_AUDIO;
|
extern bool ALLOW_REALTEK_AUDIO;
|
||||||
extern bool NATIVE_TOUCH;
|
extern bool NATIVE_TOUCH;
|
||||||
|
|
||||||
|
static inline bool arena_subscreen_landscape() {
|
||||||
|
return ARENA_SUBSCREEN_LANDSCAPE != ArenaSubscreenLandscape::Off;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool arena_subscreen_shows_sides() {
|
||||||
|
return ARENA_SUBSCREEN_LANDSCAPE == ArenaSubscreenLandscape::All;
|
||||||
|
}
|
||||||
|
|
||||||
// arena SMALL subscreen (touch panel) resolution
|
// arena SMALL subscreen (touch panel) resolution
|
||||||
static constexpr int ARENA_SUBSCREEN_WIDTH = 800;
|
static constexpr int ARENA_SUBSCREEN_WIDTH = 800;
|
||||||
static constexpr int ARENA_SUBSCREEN_HEIGHT = 1280;
|
static constexpr int ARENA_SUBSCREEN_HEIGHT = 1280;
|
||||||
|
|
||||||
|
// used when a landscape monitor drives the SMALL head and -forceressub is unset
|
||||||
|
static constexpr int ARENA_SUBSCREEN_LANDSCAPE_WIDTH = 1920;
|
||||||
|
static constexpr int ARENA_SUBSCREEN_LANDSCAPE_HEIGHT = 1080;
|
||||||
|
|
||||||
|
// resolution the SMALL head actually runs at; the portrait panel size unless
|
||||||
|
// -forceressub or the landscape option overrides it
|
||||||
|
std::pair<UINT, UINT> arena_subscreen_host_size();
|
||||||
|
|
||||||
|
// area of the host the portrait subscreen occupies, centered and aspect-preserved;
|
||||||
|
// whatever is left over on either side stays black
|
||||||
|
RECT arena_subscreen_content_rect(LONG host_width, LONG host_height);
|
||||||
|
|
||||||
class GitaDoraGame : public games::Game {
|
class GitaDoraGame : public games::Game {
|
||||||
public:
|
public:
|
||||||
GitaDoraGame();
|
GitaDoraGame();
|
||||||
|
|||||||
@@ -1033,14 +1033,6 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
|||||||
|
|
||||||
return D3DERR_INVALIDCALL;
|
return D3DERR_INVALIDCALL;
|
||||||
}
|
}
|
||||||
if (gfdm_two_head_exclusive() && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
|
|
||||||
log_warning(
|
|
||||||
"graphics::d3d9",
|
|
||||||
"-forceressub is unavailable; SMALL must remain {}x{}",
|
|
||||||
GFDM_SMALL_WIDTH,
|
|
||||||
GFDM_SMALL_HEIGHT);
|
|
||||||
return D3DERR_INVALIDCALL;
|
|
||||||
}
|
|
||||||
|
|
||||||
DWORD orig_behavior_flags = BehaviorFlags;
|
DWORD orig_behavior_flags = BehaviorFlags;
|
||||||
size_t num_adapters = 1;
|
size_t num_adapters = 1;
|
||||||
@@ -1096,7 +1088,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
|||||||
params->BackBufferHeight, params->BackBufferWidth);
|
params->BackBufferHeight, params->BackBufferWidth);
|
||||||
std::swap(params->BackBufferWidth, params->BackBufferHeight);
|
std::swap(params->BackBufferWidth, params->BackBufferHeight);
|
||||||
}
|
}
|
||||||
} else if (i == 1 && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
|
} else if (i == 1 && !gfdm_two_head_exclusive()
|
||||||
|
&& GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
|
||||||
log_misc(
|
log_misc(
|
||||||
"graphics::d3d9",
|
"graphics::d3d9",
|
||||||
"use custom sub resolution {}x{} => {}x{}",
|
"use custom sub resolution {}x{} => {}x{}",
|
||||||
@@ -1140,7 +1133,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
|||||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||||
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height);
|
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height);
|
||||||
}
|
}
|
||||||
} else if (i == 1 && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
|
} else if (i == 1 && !gfdm_two_head_exclusive()
|
||||||
|
&& GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
|
||||||
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first;
|
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first;
|
||||||
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second;
|
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second;
|
||||||
}
|
}
|
||||||
@@ -1290,14 +1284,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
|||||||
ppReturnedDeviceInterface);
|
ppReturnedDeviceInterface);
|
||||||
|
|
||||||
if (SUCCEEDED(result) && gfdm_two_head_exclusive()) {
|
if (SUCCEEDED(result) && gfdm_two_head_exclusive()) {
|
||||||
pPresentationParameters[0] = gfdm_parameters.presentation_parameters[0];
|
gfdm_publish_two_head_parameters(
|
||||||
pPresentationParameters[gfdm_parameters.logical_small_swapchain] =
|
pPresentationParameters,
|
||||||
gfdm_parameters.presentation_parameters[1];
|
pFullscreenDisplayMode,
|
||||||
if (pFullscreenDisplayMode != nullptr) {
|
gfdm_parameters);
|
||||||
pFullscreenDisplayMode[0] = gfdm_parameters.fullscreen_display_modes[0];
|
|
||||||
pFullscreenDisplayMode[gfdm_parameters.logical_small_swapchain] =
|
|
||||||
gfdm_parameters.fullscreen_display_modes[1];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for error
|
// check for error
|
||||||
|
|||||||
@@ -157,6 +157,9 @@ ULONG STDMETHODCALLTYPE WrappedIDirect3DDevice9::Release() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// holds a reference on the device, so it has to go before the counts are compared
|
||||||
|
this->gfdm_small_head.release();
|
||||||
|
|
||||||
d3d9_readback::release_device_resources(this->pReal);
|
d3d9_readback::release_device_resources(this->pReal);
|
||||||
|
|
||||||
if (overlay::ENABLED) {
|
if (overlay::ENABLED) {
|
||||||
@@ -634,6 +637,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::Reset(
|
|||||||
overlay::OVERLAY->reset_invalidate();
|
overlay::OVERLAY->reset_invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gfdm_small_head.release();
|
||||||
|
|
||||||
// Reset refuses to run while any default pool resource is outstanding
|
// Reset refuses to run while any default pool resource is outstanding
|
||||||
d3d9_readback::discard_snapshot_targets(pReal);
|
d3d9_readback::discard_snapshot_targets(pReal);
|
||||||
|
|
||||||
@@ -662,6 +667,12 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::Present(
|
|||||||
|
|
||||||
graphics_d3d9_on_present(hFocusWindow, pReal, this);
|
graphics_d3d9_on_present(hFocusWindow, pReal, this);
|
||||||
|
|
||||||
|
// an adapter group device presents every head at once, so the SMALL head has to be
|
||||||
|
// composed here as well as in its own swap chain
|
||||||
|
if (gfdm_small_head.scaled()) {
|
||||||
|
gfdm_small_head.compose(this);
|
||||||
|
}
|
||||||
|
|
||||||
CHECK_RESULT(pReal->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion));
|
CHECK_RESULT(pReal->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,6 +694,12 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetBackBuffer(
|
|||||||
if (is_gfdm_two_head_exclusive()
|
if (is_gfdm_two_head_exclusive()
|
||||||
&& is_gfdm_logical_small_swapchain(iSwapChain))
|
&& is_gfdm_logical_small_swapchain(iSwapChain))
|
||||||
{
|
{
|
||||||
|
if (gfdm_small_head.scaled()
|
||||||
|
&& iBackBuffer == 0
|
||||||
|
&& Type == D3DBACKBUFFER_TYPE_MONO)
|
||||||
|
{
|
||||||
|
CHECK_RESULT(gfdm_small_head.backbuffer(pReal, ppBackBuffer));
|
||||||
|
}
|
||||||
CHECK_RESULT(pReal->GetBackBuffer(
|
CHECK_RESULT(pReal->GetBackBuffer(
|
||||||
GFDM_NATIVE_SMALL_SWAPCHAIN,
|
GFDM_NATIVE_SMALL_SWAPCHAIN,
|
||||||
iBackBuffer,
|
iBackBuffer,
|
||||||
@@ -2324,6 +2341,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ResetEx(
|
|||||||
overlay::OVERLAY->reset_invalidate();
|
overlay::OVERLAY->reset_invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gfdm_small_head.release();
|
||||||
|
|
||||||
// ResetEx refuses to run while any default pool resource is outstanding
|
// ResetEx refuses to run while any default pool resource is outstanding
|
||||||
d3d9_readback::discard_snapshot_targets(pReal);
|
d3d9_readback::discard_snapshot_targets(pReal);
|
||||||
|
|
||||||
@@ -2341,14 +2360,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ResetEx(
|
|||||||
}
|
}
|
||||||
if (is_gfdm_two_head_exclusive() && SUCCEEDED(res)) {
|
if (is_gfdm_two_head_exclusive() && SUCCEEDED(res)) {
|
||||||
gfdm_logical_small_swapchain = gfdm_parameters.logical_small_swapchain;
|
gfdm_logical_small_swapchain = gfdm_parameters.logical_small_swapchain;
|
||||||
pPresentationParameters[0] = gfdm_parameters.presentation_parameters[0];
|
gfdm_publish_two_head_parameters(
|
||||||
pPresentationParameters[gfdm_parameters.logical_small_swapchain] =
|
pPresentationParameters,
|
||||||
gfdm_parameters.presentation_parameters[1];
|
pFullscreenDisplayMode,
|
||||||
if (pFullscreenDisplayMode != nullptr) {
|
gfdm_parameters);
|
||||||
pFullscreenDisplayMode[0] = gfdm_parameters.fullscreen_display_modes[0];
|
|
||||||
pFullscreenDisplayMode[gfdm_parameters.logical_small_swapchain] =
|
|
||||||
gfdm_parameters.fullscreen_display_modes[1];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// recreate overlay
|
// recreate overlay
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
|
||||||
#include "d3d9_fake_swapchain.h"
|
#include "d3d9_fake_swapchain.h"
|
||||||
|
#include "d3d9_gfdm.h"
|
||||||
#include "d3d9_swapchain.h"
|
#include "d3d9_swapchain.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -279,6 +280,7 @@ struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex {
|
|||||||
std::array<D3DPRESENT_PARAMETERS, 4> gfdm_logical_group_parameters {};
|
std::array<D3DPRESENT_PARAMETERS, 4> gfdm_logical_group_parameters {};
|
||||||
bool gfdm_logical_group_parameters_valid = false;
|
bool gfdm_logical_group_parameters_valid = false;
|
||||||
IDirect3D9 *gfdm_parent_d3d = nullptr;
|
IDirect3D9 *gfdm_parent_d3d = nullptr;
|
||||||
|
GfdmSmallHead gfdm_small_head;
|
||||||
|
|
||||||
std::mutex gfdm_recovery_mutex;
|
std::mutex gfdm_recovery_mutex;
|
||||||
std::array<D3DPRESENT_PARAMETERS, 2> gfdm_recovery_parameters {};
|
std::array<D3DPRESENT_PARAMETERS, 2> gfdm_recovery_parameters {};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#include "games/gitadora/gitadora.h"
|
#include "games/gitadora/gitadora.h"
|
||||||
@@ -16,6 +17,54 @@ bool gfdm_two_head_exclusive() {
|
|||||||
&& !GRAPHICS_WINDOWED;
|
&& !GRAPHICS_WINDOWED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::pair<UINT, UINT> gfdm_small_head_size() {
|
||||||
|
if (!gfdm_two_head_exclusive()) {
|
||||||
|
return { GFDM_SMALL_WIDTH, GFDM_SMALL_HEIGHT };
|
||||||
|
}
|
||||||
|
return games::gitadora::arena_subscreen_host_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// scaling is only needed when the head is not the panel size the game draws into
|
||||||
|
static bool gfdm_small_head_scaled() {
|
||||||
|
const auto [width, height] = gfdm_small_head_size();
|
||||||
|
return width != GFDM_SMALL_WIDTH || height != GFDM_SMALL_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hand the resolved MAIN/SMALL heads back to the game's own array. The SMALL entry keeps
|
||||||
|
// the portrait size the game asked for: that is what it renders, and the next reset finds
|
||||||
|
// the SMALL head by matching it.
|
||||||
|
void gfdm_publish_two_head_parameters(
|
||||||
|
D3DPRESENT_PARAMETERS *logical_presentation_parameters,
|
||||||
|
D3DDISPLAYMODEEX *logical_fullscreen_display_modes,
|
||||||
|
const GfdmTwoHeadDeviceState &state)
|
||||||
|
{
|
||||||
|
if (logical_presentation_parameters == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UINT small = state.logical_small_swapchain;
|
||||||
|
const bool publish_modes = logical_fullscreen_display_modes != nullptr
|
||||||
|
&& state.fullscreen_display_modes != nullptr;
|
||||||
|
|
||||||
|
logical_presentation_parameters[0] = state.presentation_parameters[0];
|
||||||
|
logical_presentation_parameters[small] = state.presentation_parameters[1];
|
||||||
|
if (publish_modes) {
|
||||||
|
logical_fullscreen_display_modes[0] = state.fullscreen_display_modes[0];
|
||||||
|
logical_fullscreen_display_modes[small] = state.fullscreen_display_modes[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gfdm_small_head_scaled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logical_presentation_parameters[small].BackBufferWidth = GFDM_SMALL_WIDTH;
|
||||||
|
logical_presentation_parameters[small].BackBufferHeight = GFDM_SMALL_HEIGHT;
|
||||||
|
if (publish_modes) {
|
||||||
|
logical_fullscreen_display_modes[small].Width = GFDM_SMALL_WIDTH;
|
||||||
|
logical_fullscreen_display_modes[small].Height = GFDM_SMALL_HEIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HRESULT graphics_d3d9_gfdm_select_two_head_group_parameters(
|
HRESULT graphics_d3d9_gfdm_select_two_head_group_parameters(
|
||||||
const D3DPRESENT_PARAMETERS *logical_presentation_parameters,
|
const D3DPRESENT_PARAMETERS *logical_presentation_parameters,
|
||||||
const D3DDISPLAYMODEEX *logical_fullscreen_display_modes,
|
const D3DDISPLAYMODEEX *logical_fullscreen_display_modes,
|
||||||
@@ -186,6 +235,26 @@ HRESULT graphics_d3d9_gfdm_remap_two_head_group_parameters(
|
|||||||
return D3DERR_INVALIDCALL;
|
return D3DERR_INVALIDCALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the game keeps rendering the portrait subscreen; only the head it is scanned out
|
||||||
|
// on changes size, and the scaling happens when the head is presented
|
||||||
|
if (gfdm_small_head_scaled()) {
|
||||||
|
const auto [host_width, host_height] = gfdm_small_head_size();
|
||||||
|
log_info(
|
||||||
|
"graphics::d3d9",
|
||||||
|
"two-head exclusive: {} SMALL head {}x{} -> {}x{}",
|
||||||
|
operation,
|
||||||
|
secondary.BackBufferWidth,
|
||||||
|
secondary.BackBufferHeight,
|
||||||
|
host_width,
|
||||||
|
host_height);
|
||||||
|
secondary.BackBufferWidth = host_width;
|
||||||
|
secondary.BackBufferHeight = host_height;
|
||||||
|
if (fullscreen_display_modes != nullptr) {
|
||||||
|
fullscreen_display_modes[1].Width = host_width;
|
||||||
|
fullscreen_display_modes[1].Height = host_height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return D3D_OK;
|
return D3D_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,20 +387,21 @@ HRESULT validate_gfdm_two_head_exclusive(
|
|||||||
|
|
||||||
const auto &main = presentation_parameters[0];
|
const auto &main = presentation_parameters[0];
|
||||||
const auto &small_params = presentation_parameters[1];
|
const auto &small_params = presentation_parameters[1];
|
||||||
|
const auto [expected_small_width, expected_small_height] = gfdm_small_head_size();
|
||||||
if (main.Windowed || small_params.Windowed) {
|
if (main.Windowed || small_params.Windowed) {
|
||||||
log_warning(
|
log_warning(
|
||||||
"graphics::d3d9",
|
"graphics::d3d9",
|
||||||
"two-head exclusive mode requires both group heads to be fullscreen");
|
"two-head exclusive mode requires both group heads to be fullscreen");
|
||||||
return D3DERR_INVALIDCALL;
|
return D3DERR_INVALIDCALL;
|
||||||
}
|
}
|
||||||
if (small_params.BackBufferWidth != GFDM_SMALL_WIDTH
|
if (small_params.BackBufferWidth != expected_small_width
|
||||||
|| small_params.BackBufferHeight != GFDM_SMALL_HEIGHT)
|
|| small_params.BackBufferHeight != expected_small_height)
|
||||||
{
|
{
|
||||||
log_warning(
|
log_warning(
|
||||||
"graphics::d3d9",
|
"graphics::d3d9",
|
||||||
"SMALL head must be {}x{}; got {}x{}",
|
"SMALL head must be {}x{}; got {}x{}",
|
||||||
GFDM_SMALL_WIDTH,
|
expected_small_width,
|
||||||
GFDM_SMALL_HEIGHT,
|
expected_small_height,
|
||||||
small_params.BackBufferWidth,
|
small_params.BackBufferWidth,
|
||||||
small_params.BackBufferHeight);
|
small_params.BackBufferHeight);
|
||||||
return D3DERR_INVALIDCALL;
|
return D3DERR_INVALIDCALL;
|
||||||
@@ -547,6 +617,7 @@ void WrappedIDirect3DDevice9::set_gfdm_logical_group_parameters(
|
|||||||
{
|
{
|
||||||
if (presentation_parameters == nullptr) {
|
if (presentation_parameters == nullptr) {
|
||||||
gfdm_logical_group_parameters_valid = false;
|
gfdm_logical_group_parameters_valid = false;
|
||||||
|
gfdm_small_head.resolve(nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::copy_n(
|
std::copy_n(
|
||||||
@@ -554,6 +625,8 @@ void WrappedIDirect3DDevice9::set_gfdm_logical_group_parameters(
|
|||||||
gfdm_logical_group_parameters.size(),
|
gfdm_logical_group_parameters.size(),
|
||||||
gfdm_logical_group_parameters.begin());
|
gfdm_logical_group_parameters.begin());
|
||||||
gfdm_logical_group_parameters_valid = true;
|
gfdm_logical_group_parameters_valid = true;
|
||||||
|
gfdm_small_head.resolve(
|
||||||
|
&gfdm_logical_group_parameters[gfdm_logical_small_swapchain]);
|
||||||
}
|
}
|
||||||
|
|
||||||
FakeIDirect3DSwapChain9 *
|
FakeIDirect3DSwapChain9 *
|
||||||
@@ -599,6 +672,217 @@ void WrappedIDirect3DDevice9::release_gfdm_hidden_side_swapchains() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GfdmSmallHead::resolve(const D3DPRESENT_PARAMETERS *logical_small_parameters) {
|
||||||
|
release();
|
||||||
|
|
||||||
|
active = gfdm_small_head_scaled();
|
||||||
|
format = D3DFMT_X8R8G8B8;
|
||||||
|
multisample = D3DMULTISAMPLE_NONE;
|
||||||
|
multisample_quality = 0;
|
||||||
|
if (!active || logical_small_parameters == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logical_small_parameters->BackBufferFormat != D3DFMT_UNKNOWN) {
|
||||||
|
format = logical_small_parameters->BackBufferFormat;
|
||||||
|
}
|
||||||
|
multisample = logical_small_parameters->MultiSampleType;
|
||||||
|
multisample_quality = logical_small_parameters->MultiSampleQuality;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfdmSmallHead::apply_logical_size(UINT *width, UINT *height) const {
|
||||||
|
if (!active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (width != nullptr) {
|
||||||
|
*width = GFDM_SMALL_WIDTH;
|
||||||
|
}
|
||||||
|
if (height != nullptr) {
|
||||||
|
*height = GFDM_SMALL_HEIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT GfdmSmallHead::backbuffer(IDirect3DDevice9 *device, IDirect3DSurface9 **out) {
|
||||||
|
if (device == nullptr || out == nullptr || !active) {
|
||||||
|
return D3DERR_INVALIDCALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (surface == nullptr) {
|
||||||
|
const HRESULT result = device->CreateRenderTarget(
|
||||||
|
GFDM_SMALL_WIDTH,
|
||||||
|
GFDM_SMALL_HEIGHT,
|
||||||
|
format,
|
||||||
|
multisample,
|
||||||
|
multisample_quality,
|
||||||
|
FALSE,
|
||||||
|
&surface,
|
||||||
|
nullptr);
|
||||||
|
if (FAILED(result)) {
|
||||||
|
log_warning(
|
||||||
|
"graphics::d3d9",
|
||||||
|
"two-head exclusive: could not create the portrait SMALL surface, hr={}",
|
||||||
|
FMT_HRESULT(result));
|
||||||
|
surface = nullptr;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
device->ColorFill(surface, nullptr, D3DCOLOR_XRGB(0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
surface->AddRef();
|
||||||
|
*out = surface;
|
||||||
|
return D3D_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfdmSmallHead::release() {
|
||||||
|
if (surface != nullptr) {
|
||||||
|
surface->Release();
|
||||||
|
surface = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// largest centered rect inside bounds that keeps the source's aspect ratio
|
||||||
|
static RECT gfdm_fit_centered(const RECT &bounds, LONG source_width, LONG source_height) {
|
||||||
|
const LONG bounds_width = bounds.right - bounds.left;
|
||||||
|
const LONG bounds_height = bounds.bottom - bounds.top;
|
||||||
|
if (bounds_width <= 0 || bounds_height <= 0 || source_width <= 0 || source_height <= 0) {
|
||||||
|
return RECT {};
|
||||||
|
}
|
||||||
|
|
||||||
|
LONG width = MulDiv(bounds_height, source_width, source_height);
|
||||||
|
LONG height = bounds_height;
|
||||||
|
if (width > bounds_width) {
|
||||||
|
width = bounds_width;
|
||||||
|
height = MulDiv(bounds_width, source_height, source_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LONG left = bounds.left + (bounds_width - width) / 2;
|
||||||
|
const LONG top = bounds.top + (bounds_height - height) / 2;
|
||||||
|
return RECT { left, top, left + width, top + height };
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gfdm_compose_side_heads(
|
||||||
|
WrappedIDirect3DDevice9 *device,
|
||||||
|
IDirect3DSurface9 *head,
|
||||||
|
LONG host_width,
|
||||||
|
LONG host_height,
|
||||||
|
const RECT &content)
|
||||||
|
{
|
||||||
|
if (!games::gitadora::arena_subscreen_shows_sides()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RECT bars[2] = {
|
||||||
|
{ 0, 0, content.left, host_height },
|
||||||
|
{ content.right, 0, host_width, host_height },
|
||||||
|
};
|
||||||
|
|
||||||
|
for (UINT swapchain = 1; swapchain < GFDM_LOGICAL_HEAD_COUNT; swapchain++) {
|
||||||
|
if (!device->is_gfdm_logical_side_swapchain(swapchain)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nothing has been drawn yet if the game never asked for this head's back buffer
|
||||||
|
const auto *chain = device->fake_sub_swapchain[
|
||||||
|
device->gfdm_hidden_side_swapchain_slot(swapchain)];
|
||||||
|
if (chain == nullptr || chain->render_targets.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the head's own window is the only thing that says which side it is, so an
|
||||||
|
// unnamed one is left out rather than guessed at and possibly mirrored
|
||||||
|
const char *name = graphics_gitadora_window_name(
|
||||||
|
device->gfdm_logical_group_parameters[swapchain].hDeviceWindow);
|
||||||
|
const bool named_left = name != nullptr && strcmp(name, "LEFT") == 0;
|
||||||
|
const bool named_right = name != nullptr && strcmp(name, "RIGHT") == 0;
|
||||||
|
if (!named_left && !named_right) {
|
||||||
|
static std::once_flag warned;
|
||||||
|
std::call_once(warned, [] {
|
||||||
|
log_warning(
|
||||||
|
"graphics::d3d9",
|
||||||
|
"two-head exclusive: a side head has no LEFT or RIGHT window, "
|
||||||
|
"leaving its bar black");
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RECT &bar = bars[named_left ? 0 : 1];
|
||||||
|
if (bar.right <= bar.left) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDirect3DSurface9 *source = chain->render_targets[0];
|
||||||
|
D3DSURFACE_DESC source_desc {};
|
||||||
|
if (FAILED(source->GetDesc(&source_desc))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RECT target = gfdm_fit_centered(
|
||||||
|
bar,
|
||||||
|
static_cast<LONG>(source_desc.Width),
|
||||||
|
static_cast<LONG>(source_desc.Height));
|
||||||
|
const HRESULT result = device->pReal->StretchRect(
|
||||||
|
source,
|
||||||
|
nullptr,
|
||||||
|
head,
|
||||||
|
&target,
|
||||||
|
D3DTEXF_LINEAR);
|
||||||
|
if (FAILED(result)) {
|
||||||
|
static std::once_flag warned;
|
||||||
|
std::call_once(warned, [result] {
|
||||||
|
log_warning(
|
||||||
|
"graphics::d3d9",
|
||||||
|
"two-head exclusive: could not draw a side head into the bars, hr={}",
|
||||||
|
FMT_HRESULT(result));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT GfdmSmallHead::compose(WrappedIDirect3DDevice9 *device) {
|
||||||
|
if (device == nullptr || device->pReal == nullptr) {
|
||||||
|
return D3DERR_INVALIDCALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDirect3DDevice9 *real = device->pReal;
|
||||||
|
IDirect3DSurface9 *proxy = nullptr;
|
||||||
|
HRESULT result = backbuffer(real, &proxy);
|
||||||
|
if (FAILED(result)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDirect3DSurface9 *head = nullptr;
|
||||||
|
result = real->GetBackBuffer(
|
||||||
|
GFDM_NATIVE_SMALL_SWAPCHAIN,
|
||||||
|
0,
|
||||||
|
D3DBACKBUFFER_TYPE_MONO,
|
||||||
|
&head);
|
||||||
|
if (FAILED(result)) {
|
||||||
|
proxy->Release();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3DSURFACE_DESC desc {};
|
||||||
|
result = head->GetDesc(&desc);
|
||||||
|
if (SUCCEEDED(result)) {
|
||||||
|
const auto host_width = static_cast<LONG>(desc.Width);
|
||||||
|
const auto host_height = static_cast<LONG>(desc.Height);
|
||||||
|
const RECT content = games::gitadora::arena_subscreen_content_rect(
|
||||||
|
host_width,
|
||||||
|
host_height);
|
||||||
|
|
||||||
|
// discard swap effect leaves the whole head undefined every frame, so the bars
|
||||||
|
// have to be repainted along with the image
|
||||||
|
real->ColorFill(head, nullptr, D3DCOLOR_XRGB(0, 0, 0));
|
||||||
|
result = real->StretchRect(proxy, nullptr, head, &content, D3DTEXF_LINEAR);
|
||||||
|
|
||||||
|
gfdm_compose_side_heads(device, head, host_width, host_height, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
head->Release();
|
||||||
|
proxy->Release();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void WrappedIDirect3DDevice9::gfdm_disarm_present_mode_recovery() {
|
void WrappedIDirect3DDevice9::gfdm_disarm_present_mode_recovery() {
|
||||||
std::lock_guard<std::mutex> lock(gfdm_recovery_mutex);
|
std::lock_guard<std::mutex> lock(gfdm_recovery_mutex);
|
||||||
gfdm_recovery_armed = false;
|
gfdm_recovery_armed = false;
|
||||||
|
|||||||
@@ -13,6 +13,36 @@ inline constexpr UINT GFDM_SMALL_HEIGHT = games::gitadora::ARENA_SUBSCREEN_HEIGH
|
|||||||
inline constexpr UINT GFDM_LOGICAL_HEAD_COUNT = 4;
|
inline constexpr UINT GFDM_LOGICAL_HEAD_COUNT = 4;
|
||||||
inline constexpr UINT GFDM_NATIVE_SMALL_SWAPCHAIN = 1;
|
inline constexpr UINT GFDM_NATIVE_SMALL_SWAPCHAIN = 1;
|
||||||
|
|
||||||
|
struct WrappedIDirect3DDevice9;
|
||||||
|
|
||||||
|
// Owns the portrait surface the game draws into while the SMALL head is scanned out at a
|
||||||
|
// different resolution, and composes it onto the real head. Allocates nothing and reports
|
||||||
|
// nothing until resolve() finds a head that is not the panel size.
|
||||||
|
struct GfdmSmallHead {
|
||||||
|
GfdmSmallHead() = default;
|
||||||
|
~GfdmSmallHead() { release(); }
|
||||||
|
GfdmSmallHead(const GfdmSmallHead &) = delete;
|
||||||
|
GfdmSmallHead &operator=(const GfdmSmallHead &) = delete;
|
||||||
|
|
||||||
|
// settled with the device, so it cannot change under the game mid-session
|
||||||
|
void resolve(const D3DPRESENT_PARAMETERS *logical_small_parameters);
|
||||||
|
bool scaled() const { return active; }
|
||||||
|
|
||||||
|
HRESULT backbuffer(IDirect3DDevice9 *device, IDirect3DSurface9 **out);
|
||||||
|
HRESULT compose(WrappedIDirect3DDevice9 *device);
|
||||||
|
void release();
|
||||||
|
|
||||||
|
// the game only ever sees the portrait size it asked for
|
||||||
|
void apply_logical_size(UINT *width, UINT *height) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool active = false;
|
||||||
|
D3DFORMAT format = D3DFMT_X8R8G8B8;
|
||||||
|
D3DMULTISAMPLE_TYPE multisample = D3DMULTISAMPLE_NONE;
|
||||||
|
DWORD multisample_quality = 0;
|
||||||
|
IDirect3DSurface9 *surface = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
struct GfdmTwoHeadDeviceState {
|
struct GfdmTwoHeadDeviceState {
|
||||||
explicit GfdmTwoHeadDeviceState(
|
explicit GfdmTwoHeadDeviceState(
|
||||||
D3DPRESENT_PARAMETERS *presentation_parameters,
|
D3DPRESENT_PARAMETERS *presentation_parameters,
|
||||||
@@ -41,6 +71,10 @@ struct GfdmTwoHeadDeviceState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool gfdm_two_head_exclusive();
|
bool gfdm_two_head_exclusive();
|
||||||
|
void gfdm_publish_two_head_parameters(
|
||||||
|
D3DPRESENT_PARAMETERS *logical_presentation_parameters,
|
||||||
|
D3DDISPLAYMODEEX *logical_fullscreen_display_modes,
|
||||||
|
const GfdmTwoHeadDeviceState &state);
|
||||||
bool is_fake_subscreen_adapter(UINT adapter);
|
bool is_fake_subscreen_adapter(UINT adapter);
|
||||||
void get_fake_subscreen_display_mode(UINT adapter, D3DDISPLAYMODE *mode);
|
void get_fake_subscreen_display_mode(UINT adapter, D3DDISPLAYMODE *mode);
|
||||||
void get_fake_subscreen_display_mode_ex(UINT adapter, D3DDISPLAYMODEEX *mode);
|
void get_fake_subscreen_display_mode_ex(UINT adapter, D3DDISPLAYMODEEX *mode);
|
||||||
|
|||||||
@@ -21,6 +21,12 @@
|
|||||||
} \
|
} \
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
// inert unless this is the SMALL head and it is being scaled onto a differently sized monitor
|
||||||
|
static bool scales_small_head(const WrappedIDirect3DSwapChain9 *chain) {
|
||||||
|
return chain->native_group_head == WrappedIDirect3DSwapChain9::NativeGroupHead::Small
|
||||||
|
&& chain->pDev->gfdm_small_head.scaled();
|
||||||
|
}
|
||||||
|
|
||||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::QueryInterface(REFIID riid, void **ppvObj) {
|
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::QueryInterface(REFIID riid, void **ppvObj) {
|
||||||
if (ppvObj == nullptr) {
|
if (ppvObj == nullptr) {
|
||||||
return E_POINTER;
|
return E_POINTER;
|
||||||
@@ -89,6 +95,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::Present(const RECT *pSourc
|
|||||||
graphics_d3d9_on_present(pDev->hFocusWindow, pDev->pReal, pDev);
|
graphics_d3d9_on_present(pDev->hFocusWindow, pDev->pReal, pDev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (scales_small_head(this)) {
|
||||||
|
pDev->gfdm_small_head.compose(pDev);
|
||||||
|
}
|
||||||
|
|
||||||
HRESULT result = pReal->Present(
|
HRESULT result = pReal->Present(
|
||||||
pSourceRect,
|
pSourceRect,
|
||||||
pDestRect,
|
pDestRect,
|
||||||
@@ -138,6 +148,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetFrontBufferData(IDirect
|
|||||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetBackBuffer(UINT iBackBuffer, D3DBACKBUFFER_TYPE Type,
|
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetBackBuffer(UINT iBackBuffer, D3DBACKBUFFER_TYPE Type,
|
||||||
IDirect3DSurface9 **ppBackBuffer)
|
IDirect3DSurface9 **ppBackBuffer)
|
||||||
{
|
{
|
||||||
|
if (scales_small_head(this) && iBackBuffer == 0 && Type == D3DBACKBUFFER_TYPE_MONO) {
|
||||||
|
CHECK_RESULT(pDev->gfdm_small_head.backbuffer(pDev->pReal, ppBackBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
CHECK_RESULT(pReal->GetBackBuffer(iBackBuffer, Type, ppBackBuffer));
|
CHECK_RESULT(pReal->GetBackBuffer(iBackBuffer, Type, ppBackBuffer));
|
||||||
}
|
}
|
||||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetRasterStatus(D3DRASTER_STATUS *pRasterStatus) {
|
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetRasterStatus(D3DRASTER_STATUS *pRasterStatus) {
|
||||||
@@ -159,7 +173,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetDevice(IDirect3DDevice9
|
|||||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetPresentParameters(
|
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetPresentParameters(
|
||||||
D3DPRESENT_PARAMETERS *pPresentationParameters)
|
D3DPRESENT_PARAMETERS *pPresentationParameters)
|
||||||
{
|
{
|
||||||
CHECK_RESULT(pReal->GetPresentParameters(pPresentationParameters));
|
HRESULT result = pReal->GetPresentParameters(pPresentationParameters);
|
||||||
|
if (SUCCEEDED(result) && pPresentationParameters != nullptr && scales_small_head(this)) {
|
||||||
|
pDev->gfdm_small_head.apply_logical_size(
|
||||||
|
&pPresentationParameters->BackBufferWidth,
|
||||||
|
&pPresentationParameters->BackBufferHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_RESULT(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -233,6 +233,10 @@ bool graphics_gitadora_has_dedicated_subscreen() {
|
|||||||
return GFDM_SUBSCREEN_WINDOW != nullptr;
|
return GFDM_SUBSCREEN_WINDOW != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *graphics_gitadora_window_name(HWND hWnd) {
|
||||||
|
return gitadora_window_name_for_hwnd(hWnd);
|
||||||
|
}
|
||||||
|
|
||||||
bool graphics_gitadora_prepare_two_head_device_window(
|
bool graphics_gitadora_prepare_two_head_device_window(
|
||||||
HWND hWnd, HMONITOR target_monitor, UINT desired_width, UINT desired_height) {
|
HWND hWnd, HMONITOR target_monitor, UINT desired_width, UINT desired_height) {
|
||||||
if (hWnd == nullptr || !IsWindow(hWnd)) {
|
if (hWnd == nullptr || !IsWindow(hWnd)) {
|
||||||
|
|||||||
@@ -122,6 +122,9 @@ extern bool D3D9_DEVICE_HOOK_DISABLE;
|
|||||||
void graphics_init();
|
void graphics_init();
|
||||||
void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParameters);
|
void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParameters);
|
||||||
bool graphics_gitadora_has_dedicated_subscreen();
|
bool graphics_gitadora_has_dedicated_subscreen();
|
||||||
|
|
||||||
|
// "GITADORA", "LEFT", "RIGHT", "SMALL", or nullptr for anything else
|
||||||
|
const char *graphics_gitadora_window_name(HWND hWnd);
|
||||||
// The native GITADORA two-head D3D9 group uses the game's named SMALL
|
// The native GITADORA two-head D3D9 group uses the game's named SMALL
|
||||||
// device window for the native physical SMALL head. The game requests
|
// device window for the native physical SMALL head. The game requests
|
||||||
// D3DCREATE_NOWINDOWCHANGES, so this host must be made borderless and sized
|
// D3DCREATE_NOWINDOWCHANGES, so this host must be made borderless and sized
|
||||||
|
|||||||
@@ -671,6 +671,16 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gitadora arena sub layout
|
||||||
|
if (options[launcher::Options::GitaDoraArenaSubLayout].is_active()) {
|
||||||
|
const auto text = options[launcher::Options::GitaDoraArenaSubLayout].value_text();
|
||||||
|
if (text == "landscape") {
|
||||||
|
games::gitadora::ARENA_SUBSCREEN_LANDSCAPE = games::gitadora::ArenaSubscreenLandscape::Small;
|
||||||
|
} else if (text == "combine") {
|
||||||
|
games::gitadora::ARENA_SUBSCREEN_LANDSCAPE = games::gitadora::ArenaSubscreenLandscape::All;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (options[launcher::Options::GitaDoraWailHold].is_active()) {
|
if (options[launcher::Options::GitaDoraWailHold].is_active()) {
|
||||||
socd::TILT_HOLD_MS = options[launcher::Options::GitaDoraWailHold].value_uint32();
|
socd::TILT_HOLD_MS = options[launcher::Options::GitaDoraWailHold].value_uint32();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1330,6 +1330,25 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
},
|
},
|
||||||
.quick_setting_category = "Game",
|
.quick_setting_category = "Game",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// GitaDoraArenaSubLayout
|
||||||
|
.title = "GitaDora Arena Subscreen Layout (EXPERIMENTAL)",
|
||||||
|
.name = "gdasublayout",
|
||||||
|
.desc = "For Arena Model full screen two-window mode: select the image layout on the second monitor.\n\n"
|
||||||
|
"portrait (default): default subscreen @ 800x1280\n\n"
|
||||||
|
"landscape: pillarboxed subscreen @ 1920x1080\n\n"
|
||||||
|
"combine: LEFT + SMALL + RIGHT screens side by side @ 1920x1080\n\n"
|
||||||
|
"For landscape and combine, -forceressub can be used to override the resolution.",
|
||||||
|
.type = OptionType::Enum,
|
||||||
|
.game_name = "GitaDora",
|
||||||
|
.category = "Game Options",
|
||||||
|
.elements = {
|
||||||
|
{"portrait", ""},
|
||||||
|
{"landscape", ""},
|
||||||
|
{"combine", ""}
|
||||||
|
},
|
||||||
|
.quick_setting_category = "Game",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// GitaDoraArenaAsioDriver
|
// GitaDoraArenaAsioDriver
|
||||||
.title = "GitaDora Arena ASIO driver",
|
.title = "GitaDora Arena ASIO driver",
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ namespace launcher {
|
|||||||
GitaDoraSubOverlaySize,
|
GitaDoraSubOverlaySize,
|
||||||
GitaDoraArenaSingleWindow,
|
GitaDoraArenaSingleWindow,
|
||||||
GitaDoraArenaWindowLayout,
|
GitaDoraArenaWindowLayout,
|
||||||
|
GitaDoraArenaSubLayout,
|
||||||
GitaDoraArenaAsioDriver,
|
GitaDoraArenaAsioDriver,
|
||||||
GitaDoraArenaRealtekAccess,
|
GitaDoraArenaRealtekAccess,
|
||||||
LoadJubeatModule,
|
LoadJubeatModule,
|
||||||
@@ -323,7 +324,7 @@ namespace launcher {
|
|||||||
OBSWebSocketPassword,
|
OBSWebSocketPassword,
|
||||||
OBSWebSocketDebug,
|
OBSWebSocketDebug,
|
||||||
ScreenshotIncludeOverlay,
|
ScreenshotIncludeOverlay,
|
||||||
ScreenshotSubscreens
|
ScreenshotSubscreens,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class OptionsCategory {
|
enum class OptionsCategory {
|
||||||
|
|||||||
@@ -1607,6 +1607,7 @@ typedef enum SPICE_SDK_DANCERUSH_BUTTONS {
|
|||||||
DRS_Button_P2_Down,
|
DRS_Button_P2_Down,
|
||||||
DRS_Button_P2_Left,
|
DRS_Button_P2_Left,
|
||||||
DRS_Button_P2_Right,
|
DRS_Button_P2_Right,
|
||||||
|
DRS_Button_DownMotion,
|
||||||
} SPICE_SDK_DANCERUSH_BUTTONS;
|
} SPICE_SDK_DANCERUSH_BUTTONS;
|
||||||
|
|
||||||
typedef enum SPICE_SDK_DANCERUSH_LIGHTS {
|
typedef enum SPICE_SDK_DANCERUSH_LIGHTS {
|
||||||
|
|||||||
Reference in New Issue
Block a user