gitadora: fix synthetic touch (mouse and api) (#867)
## Link to GitHub Issue or related Pull Request, if one exists #0 ## Description of change Fix mouse and touch API handling for gitadora. * hook native touch in every arena window mode (previously only using wintouchemu for 1 windowed mode) * publish the touch window (SMALL panel, or the main window drawing the overlay) and only attach injection there * route touch on the SMALL window straight through instead of the overlay transform, and accept the mouse only there * fix spiceapi by forcing 800x1280 touch canvas size * run API capture before the subscreen present to fix api not showing sub image ## Testing Test: gitadora 1/2/4 windowed mode, fullscreen iidx /sdvx / popn windowed/fullscreen for regressions
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
#include "touch/native/inject.h"
|
#include "touch/native/inject.h"
|
||||||
#include "touch/native/nativetouchhook.h"
|
#include "touch/native/nativetouchhook.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
#include "games/gitadora/gitadora.h"
|
||||||
#include "games/iidx/iidx.h"
|
#include "games/iidx/iidx.h"
|
||||||
|
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
@@ -69,6 +70,10 @@ namespace api::modules {
|
|||||||
// pop'n music API touch surface
|
// pop'n music API touch surface
|
||||||
native_canvas_w = 1280;
|
native_canvas_w = 1280;
|
||||||
native_canvas_h = 800;
|
native_canvas_h = 800;
|
||||||
|
} else if (games::gitadora::is_arena_model()) {
|
||||||
|
// GITADORA arena SMALL subscreen, either in its own window or in the overlay
|
||||||
|
native_canvas_w = games::gitadora::ARENA_SUBSCREEN_WIDTH;
|
||||||
|
native_canvas_h = games::gitadora::ARENA_SUBSCREEN_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
functions["read"] = std::bind(&Touch::read, this, _1, _2);
|
functions["read"] = std::bind(&Touch::read, this, _1, _2);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ namespace games::gitadora {
|
|||||||
bool ARENA_TWO_HEAD_EXCLUSIVE = false;
|
bool ARENA_TWO_HEAD_EXCLUSIVE = false;
|
||||||
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;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prevent GitaDora from creating folders on F drive
|
* Prevent GitaDora from creating folders on F drive
|
||||||
@@ -809,18 +810,17 @@ namespace games::gitadora {
|
|||||||
hooks::audio::INJECT_FAKE_REALTEK_AUDIO = true;
|
hooks::audio::INJECT_FAKE_REALTEK_AUDIO = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Single-window mode needs touch injection for its overlay. Two-head fullscreen
|
// touch injection drives mouse-as-touch and API touch for the subscreen,
|
||||||
// mode uses the real SMALL output, so it only needs the display-topology shim.
|
// no matter whether it is drawn by the overlay (single-window mode) or by
|
||||||
if (GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
// the dedicated SMALL window
|
||||||
// enable touch hook for subscreen overlay
|
NATIVE_TOUCH = !wintouchemu::FORCE &&
|
||||||
const auto native_touch_ready = !wintouchemu::FORCE &&
|
|
||||||
nativetouch::hook(avs::game::DLL_INSTANCE);
|
nativetouch::hook(avs::game::DLL_INSTANCE);
|
||||||
if (!native_touch_ready) {
|
if (!NATIVE_TOUCH && GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||||
|
// the legacy fallback can only feed the subscreen overlay
|
||||||
wintouchemu::FORCE = true;
|
wintouchemu::FORCE = true;
|
||||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||||
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
|
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#if !SPICE_XP
|
#if !SPICE_XP
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ namespace games::gitadora {
|
|||||||
extern bool ARENA_TWO_HEAD_EXCLUSIVE;
|
extern bool ARENA_TWO_HEAD_EXCLUSIVE;
|
||||||
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;
|
||||||
|
|
||||||
|
// arena SMALL subscreen (touch panel) resolution
|
||||||
|
static constexpr int ARENA_SUBSCREEN_WIDTH = 800;
|
||||||
|
static constexpr int ARENA_SUBSCREEN_HEIGHT = 1280;
|
||||||
|
|
||||||
class GitaDoraGame : public games::Game {
|
class GitaDoraGame : public games::Game {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -1520,6 +1520,10 @@ void graphics_d3d9_on_present(
|
|||||||
graphics_d3d9_process_screenshot(device, wrapped_device);
|
graphics_d3d9_process_screenshot(device, wrapped_device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API capture always includes the overlay; it must run before the subscreen present
|
||||||
|
// below, which leaves the arena SMALL back buffer black
|
||||||
|
graphics_d3d9_process_capture(device, wrapped_device);
|
||||||
|
|
||||||
// for IIDX TDJ / SDVX UFC, handle subscreen
|
// for IIDX TDJ / SDVX UFC, handle subscreen
|
||||||
const bool is_vm = games::sdvx::is_valkyrie_model();
|
const bool is_vm = games::sdvx::is_valkyrie_model();
|
||||||
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;
|
||||||
@@ -1533,9 +1537,6 @@ void graphics_d3d9_on_present(
|
|||||||
if (is_mfc) {
|
if (is_mfc) {
|
||||||
wintouchemu::update();
|
wintouchemu::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// API capture always includes the overlay
|
|
||||||
graphics_d3d9_process_capture(device, wrapped_device);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params) {
|
void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params) {
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
|
|
||||||
#include <d3d9.h>
|
#include <d3d9.h>
|
||||||
|
|
||||||
|
#include "games/gitadora/gitadora.h"
|
||||||
|
|
||||||
inline constexpr UINT GFDM_SIDE_WIDTH = 1080;
|
inline constexpr UINT GFDM_SIDE_WIDTH = 1080;
|
||||||
inline constexpr UINT GFDM_SIDE_HEIGHT = 1920;
|
inline constexpr UINT GFDM_SIDE_HEIGHT = 1920;
|
||||||
inline constexpr UINT GFDM_SMALL_WIDTH = 800;
|
inline constexpr UINT GFDM_SMALL_WIDTH = games::gitadora::ARENA_SUBSCREEN_WIDTH;
|
||||||
inline constexpr UINT GFDM_SMALL_HEIGHT = 1280;
|
inline constexpr UINT GFDM_SMALL_HEIGHT = games::gitadora::ARENA_SUBSCREEN_HEIGHT;
|
||||||
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;
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "misc/wintouchemu.h"
|
#include "misc/wintouchemu.h"
|
||||||
#include "touch/native/inject.h"
|
#include "touch/native/inject.h"
|
||||||
|
#include "touch/native/nativetouchhook.h"
|
||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
|
|
||||||
@@ -216,6 +217,18 @@ static void gitadora_remember_window(HWND hWnd, const std::string &window_name)
|
|||||||
} else if (window_name == "SMALL") {
|
} else if (window_name == "SMALL") {
|
||||||
GFDM_SUBSCREEN_WINDOW = hWnd;
|
GFDM_SUBSCREEN_WINDOW = hWnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// touch belongs to the SMALL panel when it exists, otherwise to the main window
|
||||||
|
// that draws the subscreen overlay
|
||||||
|
const bool hosts_touch = window_name == "SMALL" ||
|
||||||
|
(window_name == "GITADORA" && !graphics_gitadora_has_dedicated_subscreen());
|
||||||
|
if (nativetouch::is_hooked() && hWnd != nullptr && hosts_touch) {
|
||||||
|
nativetouch::inject::set_preferred_injection_window(hWnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool graphics_gitadora_has_dedicated_subscreen() {
|
||||||
|
return GFDM_SUBSCREEN_WINDOW != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool graphics_gitadora_prepare_two_head_device_window(
|
bool graphics_gitadora_prepare_two_head_device_window(
|
||||||
@@ -731,6 +744,11 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
|||||||
if (GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
if (GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||||
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
|
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the dedicated SMALL window is the touch panel; mouse and API touch target it
|
||||||
|
if (nativetouch::is_hooked() && result != nullptr) {
|
||||||
|
nativetouch::inject::register_and_attach_window(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (is_gfdm_window && GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
if (is_gfdm_window && GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||||
gitadora_force_window_style(result);
|
gitadora_force_window_style(result);
|
||||||
@@ -1320,7 +1338,9 @@ void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParamet
|
|||||||
const bool native_touch_overlay =
|
const bool native_touch_overlay =
|
||||||
(games::iidx::NATIVE_TOUCH && games::iidx::TDJ_MODE && !GRAPHICS_IIDX_WSUB) ||
|
(games::iidx::NATIVE_TOUCH && games::iidx::TDJ_MODE && !GRAPHICS_IIDX_WSUB) ||
|
||||||
(games::popn::NATIVE_TOUCH &&
|
(games::popn::NATIVE_TOUCH &&
|
||||||
games::popn::is_pikapika_model() && GRAPHICS_PREVENT_SECONDARY_WINDOWS);
|
games::popn::is_pikapika_model() && GRAPHICS_PREVENT_SECONDARY_WINDOWS) ||
|
||||||
|
(games::gitadora::NATIVE_TOUCH &&
|
||||||
|
games::gitadora::is_arena_model() && GRAPHICS_PREVENT_SECONDARY_WINDOWS);
|
||||||
if (native_touch_overlay) {
|
if (native_touch_overlay) {
|
||||||
nativetouch::inject::register_and_attach_window(hWnd);
|
nativetouch::inject::register_and_attach_window(hWnd);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ 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();
|
||||||
// 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
|
||||||
|
|||||||
@@ -123,6 +123,30 @@ namespace nativetouch::inject {
|
|||||||
// asks the touch window thread to send an UPDATE when the game loop runs elsewhere
|
// asks the touch window thread to send an UPDATE when the game loop runs elsewhere
|
||||||
static UINT contact_refresh_message;
|
static UINT contact_refresh_message;
|
||||||
|
|
||||||
|
// the window that shows the touch surface; contacts can be registered from any thread,
|
||||||
|
// so this is published by the graphics window hooks rather than derived from the globals
|
||||||
|
static std::atomic<HWND> preferred_injection_window { nullptr };
|
||||||
|
|
||||||
|
void set_preferred_injection_window(HWND window) {
|
||||||
|
preferred_injection_window.store(window, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
// a game that publishes its touch surface needs injection only on that window
|
||||||
|
static bool is_published_touch_window(HWND window) {
|
||||||
|
const auto preferred = preferred_injection_window.load(std::memory_order_acquire);
|
||||||
|
return preferred == nullptr || window == preferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
// games can register several windows for touch; keep synthetic contacts on the one
|
||||||
|
// that actually shows the touch surface
|
||||||
|
static bool is_preferred_injection_window(HWND window) {
|
||||||
|
if (GRAPHICS_IIDX_WSUB) {
|
||||||
|
return window == TDJ_SUBSCREEN_WINDOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_published_touch_window(window);
|
||||||
|
}
|
||||||
|
|
||||||
// submit one synthetic contact frame to Windows touch injection
|
// submit one synthetic contact frame to Windows touch injection
|
||||||
static bool inject_touch_frame(
|
static bool inject_touch_frame(
|
||||||
POINT position, POINTER_FLAGS pointer_flags, bool retry_if_not_ready = false) {
|
POINT position, POINTER_FLAGS pointer_flags, bool retry_if_not_ready = false) {
|
||||||
@@ -325,11 +349,22 @@ namespace nativetouch::inject {
|
|||||||
if (contact_state.input_window == window) {
|
if (contact_state.input_window == window) {
|
||||||
release_active_contact();
|
release_active_contact();
|
||||||
}
|
}
|
||||||
|
|
||||||
HWND expected_window = window;
|
HWND expected_window = window;
|
||||||
injection_window.compare_exchange_strong(
|
injection_window.compare_exchange_strong(
|
||||||
expected_window, nullptr, std::memory_order_acq_rel);
|
expected_window, nullptr, std::memory_order_acq_rel);
|
||||||
|
|
||||||
|
HWND expected_preferred_window = window;
|
||||||
|
preferred_injection_window.compare_exchange_strong(
|
||||||
|
expected_preferred_window, nullptr, std::memory_order_acq_rel);
|
||||||
|
|
||||||
|
if (GRAPHICS_IIDX_WSUB) {
|
||||||
|
HWND expected_delivery_window = window;
|
||||||
|
touch_delivery_window.compare_exchange_strong(
|
||||||
|
expected_delivery_window, nullptr, std::memory_order_acq_rel);
|
||||||
|
}
|
||||||
|
|
||||||
contact_refresh_pending.store(false, std::memory_order_release);
|
contact_refresh_pending.store(false, std::memory_order_release);
|
||||||
touch_delivery_window.store(nullptr, std::memory_order_release);
|
|
||||||
RemoveWindowSubclass(window, touch_window_subclass_proc, subclass_id);
|
RemoveWindowSubclass(window, touch_window_subclass_proc, subclass_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,7 +405,7 @@ namespace nativetouch::inject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// publish the UI-thread target for synthetic touch requests
|
// publish the UI-thread target for synthetic touch requests
|
||||||
if (!GRAPHICS_IIDX_WSUB || window == TDJ_SUBSCREEN_WINDOW) {
|
if (is_preferred_injection_window(window)) {
|
||||||
injection_window.store(window, std::memory_order_release);
|
injection_window.store(window, std::memory_order_release);
|
||||||
}
|
}
|
||||||
log_misc(
|
log_misc(
|
||||||
@@ -403,7 +438,7 @@ namespace nativetouch::inject {
|
|||||||
// call original
|
// call original
|
||||||
const auto result = RegisterTouchWindow_orig(window, flags);
|
const auto result = RegisterTouchWindow_orig(window, flags);
|
||||||
|
|
||||||
if (result) {
|
if (result && is_published_touch_window(window)) {
|
||||||
// attach but don't register for touch messages
|
// attach but don't register for touch messages
|
||||||
// (we're already in the middle of RegisterTouchWindow as a result
|
// (we're already in the middle of RegisterTouchWindow as a result
|
||||||
// of the game calling it)
|
// of the game calling it)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ struct tagTOUCHINPUT;
|
|||||||
namespace nativetouch::inject {
|
namespace nativetouch::inject {
|
||||||
void attach_window(HWND window);
|
void attach_window(HWND window);
|
||||||
void register_and_attach_window(HWND window);
|
void register_and_attach_window(HWND window);
|
||||||
|
void set_preferred_injection_window(HWND window);
|
||||||
bool hook_available(HMODULE module);
|
bool hook_available(HMODULE module);
|
||||||
bool hook(HMODULE module);
|
bool hook(HMODULE module);
|
||||||
bool inject_synthetic_touch(POINT position, bool down);
|
bool inject_synthetic_touch(POINT position, bool down);
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ namespace nativetouch::transform {
|
|||||||
window == TDJ_SUBSCREEN_WINDOW;
|
window == TDJ_SUBSCREEN_WINDOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mouse-as-touch only applies while the cursor is over the target window
|
||||||
|
static bool is_cursor_over_window(HWND window, POINT position) {
|
||||||
|
return screen_to_game_client(window, &position);
|
||||||
|
}
|
||||||
|
|
||||||
// convert game touch coordinates to Windows desktop coordinates
|
// convert game touch coordinates to Windows desktop coordinates
|
||||||
bool game_to_screen(HWND window, POINT *position) {
|
bool game_to_screen(HWND window, POINT *position) {
|
||||||
if (settings::SYNTHETIC_TOUCH_USES_CLIENT_COORDINATES) {
|
if (settings::SYNTHETIC_TOUCH_USES_CLIENT_COORDINATES) {
|
||||||
@@ -66,7 +71,13 @@ namespace nativetouch::transform {
|
|||||||
return ClientToScreen(window, position) != FALSE;
|
return ClientToScreen(window, position) != FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool has_active_overlay_transform() {
|
static bool overlay_owns_touch_input() {
|
||||||
|
// the arena SMALL window is the touch surface whenever it exists, so the
|
||||||
|
// subscreen overlay must not claim touch input in those window modes
|
||||||
|
if (graphics_gitadora_has_dedicated_subscreen()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return overlay::OVERLAY != nullptr &&
|
return overlay::OVERLAY != nullptr &&
|
||||||
overlay::OVERLAY->get_active() &&
|
overlay::OVERLAY->get_active() &&
|
||||||
overlay::OVERLAY->has_subscreen_touch_transform();
|
overlay::OVERLAY->has_subscreen_touch_transform();
|
||||||
@@ -132,7 +143,7 @@ namespace nativetouch::transform {
|
|||||||
|
|
||||||
// check if subscreen overlay is active and can transform the touch point;
|
// check if subscreen overlay is active and can transform the touch point;
|
||||||
// if not, the touch point is valid as-is
|
// if not, the touch point is valid as-is
|
||||||
if (!has_active_overlay_transform()) {
|
if (!overlay_owns_touch_input()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,8 +160,14 @@ namespace nativetouch::transform {
|
|||||||
|
|
||||||
// exception: sdvx windowed subscreen does not use the subscreen overlay transform
|
// exception: sdvx windowed subscreen does not use the subscreen overlay transform
|
||||||
if (GRAPHICS_WINDOWED && window == SDVX_SUBSCREEN_WINDOW) {
|
if (GRAPHICS_WINDOWED && window == SDVX_SUBSCREEN_WINDOW) {
|
||||||
POINT client_position = *position;
|
return is_cursor_over_window(window, *position);
|
||||||
return screen_to_game_client(window, &client_position);
|
}
|
||||||
|
|
||||||
|
// exception: the arena SMALL window is the touch panel, so accept the mouse there
|
||||||
|
// (and only there) with the coordinates a real contact on it would produce
|
||||||
|
if (graphics_gitadora_has_dedicated_subscreen()) {
|
||||||
|
return window == GFDM_SUBSCREEN_WINDOW &&
|
||||||
|
is_cursor_over_window(window, *position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this game has a subscreen overlay that can transform touch input
|
// if this game has a subscreen overlay that can transform touch input
|
||||||
@@ -168,7 +185,7 @@ namespace nativetouch::transform {
|
|||||||
// route hardware screen coordinates through dedicated or overlay mapping and report the result
|
// route hardware screen coordinates through dedicated or overlay mapping and report the result
|
||||||
Result hardware_to_game(POINT *position) {
|
Result hardware_to_game(POINT *position) {
|
||||||
const auto dedicated_subscreen = is_tdj_dedicated_subscreen(TDJ_SUBSCREEN_WINDOW);
|
const auto dedicated_subscreen = is_tdj_dedicated_subscreen(TDJ_SUBSCREEN_WINDOW);
|
||||||
const auto active_overlay = has_active_overlay_transform();
|
const auto active_overlay = overlay_owns_touch_input();
|
||||||
|
|
||||||
// special case for SDVX landscape mode
|
// special case for SDVX landscape mode
|
||||||
if (!dedicated_subscreen && !active_overlay &&
|
if (!dedicated_subscreen && !active_overlay &&
|
||||||
|
|||||||
Reference in New Issue
Block a user