mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ec7528dac | |||
| e7822aad8e | |||
| 8140e83e46 | |||
| cc0899290e | |||
| 3012139028 | |||
| c590b87cff | |||
| 5cabd026ae | |||
| 7ccd938f9b | |||
| 88a210e82e |
@@ -411,6 +411,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
games/sdvx/sdvx_live2d.cpp
|
||||
games/sdvx/io.cpp
|
||||
games/sdvx/camera.cpp
|
||||
games/jb/bi2x_hook.cpp
|
||||
games/jb/jb.cpp
|
||||
games/jb/jb_touch.cpp
|
||||
games/jb/io.cpp
|
||||
@@ -535,6 +536,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
hooks/graphics/nvenc_hook.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_backend.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_device.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_gfdm.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_live2d.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_fake_swapchain.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_swapchain.cpp
|
||||
|
||||
@@ -239,7 +239,7 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
|
||||
// SDVX Old cabinet mode
|
||||
if (avs::game::is_model("KFC") && avs::game::SPEC[0] != 'G' && avs::game::SPEC[0] != 'H')
|
||||
answer_type = 1;
|
||||
if (avs::game::is_model("L44"))
|
||||
if (avs::game::is_model({ "L44", "T44" }))
|
||||
answer_type = 2;
|
||||
|
||||
// check answer type
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#include "keypads.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "external/rapidjson/document.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
using namespace rapidjson;
|
||||
@@ -59,7 +60,6 @@ namespace api::modules {
|
||||
// get params
|
||||
auto keypad = req.params[0].GetUint();
|
||||
auto input = std::string(req.params[1].GetString());
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// process all chars
|
||||
for (auto c : input) {
|
||||
@@ -93,11 +93,11 @@ namespace api::modules {
|
||||
|
||||
// set
|
||||
eamuse_set_keypad_overrides(keypad, state);
|
||||
timer.sleep(sleep_time);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
|
||||
|
||||
// unset
|
||||
eamuse_set_keypad_overrides(keypad, 0);
|
||||
timer.sleep(sleep_time);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#include "controller.h"
|
||||
#include "serial.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
|
||||
@@ -17,7 +18,6 @@ namespace api {
|
||||
controller->init_state(this->state);
|
||||
this->thread = new std::thread([this] () {
|
||||
log_warning("api::serial", "listening on {} (baud: {})", this->port, this->baud);
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// read buffer
|
||||
uint8_t read_buffer[16*1024];
|
||||
@@ -162,7 +162,7 @@ namespace api {
|
||||
|
||||
// slow down on reconnect
|
||||
if (this->running) {
|
||||
timer.sleep(retry_time);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(retry_time));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1114,6 +1114,7 @@ namespace avs {
|
||||
#endif
|
||||
// jubeat
|
||||
{"jubeat.dll", 0x2000000},
|
||||
{"jubeat2019.dll", 0x10000000},
|
||||
|
||||
// MUSECA
|
||||
{"museca.dll", 0xC000000},
|
||||
|
||||
@@ -591,6 +591,7 @@ namespace avs {
|
||||
// for proper reporting of Omnimix and other song packs
|
||||
if (_stricmp(EA3_MODEL, "LDJ") == 0 ||
|
||||
_stricmp(EA3_MODEL, "L44") == 0 ||
|
||||
_stricmp(EA3_MODEL, "T44") == 0 ||
|
||||
_stricmp(EA3_MODEL, "M39") == 0 ||
|
||||
_stricmp(EA3_MODEL, "KFC") == 0)
|
||||
{
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
#include "asio.h"
|
||||
#include "handle.h"
|
||||
#include "bi2x_hook.h"
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "cfg/configurator.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
|
||||
#include <ks.h>
|
||||
#include <ksmedia.h>
|
||||
|
||||
#include "cfg/configurator.h"
|
||||
#include "hooks/audio/audio.h"
|
||||
#include "hooks/audio/mme.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
@@ -29,12 +31,14 @@ namespace games::gitadora {
|
||||
|
||||
// settings
|
||||
bool TWOCHANNEL = false;
|
||||
bool DISABLE_FRAME_LIMITER = false;
|
||||
std::optional<unsigned int> CAB_TYPE = std::nullopt;
|
||||
bool P1_LEFTY = false;
|
||||
bool P2_LEFTY = false;
|
||||
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||
std::optional<socd::SocdAlgorithm> PICK_ALGO = socd::SocdAlgorithm::PreferRecent;
|
||||
std::optional<uint8_t> ARENA_WINDOW_COUNT = std::nullopt;
|
||||
bool ARENA_TWO_HEAD_EXCLUSIVE = false;
|
||||
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||||
bool ALLOW_REALTEK_AUDIO = false;
|
||||
|
||||
@@ -62,6 +66,46 @@ namespace games::gitadora {
|
||||
|
||||
return CreateDirectoryA(lpPathName, lpSecurityAttributes);
|
||||
}
|
||||
|
||||
// libshare-pj paces mainloop with separate 12 ms and 16 ms waits. these waits
|
||||
// interfere with the game's normal display synchronization on modern Windows
|
||||
// and can hold a nominal 60 FPS game near 58 FPS. locate the instruction
|
||||
// sequences at runtime so the fix does not depend on per-version file offsets.
|
||||
static void disable_mainloop_frame_limiter(HMODULE sharepj_module) {
|
||||
if (!sharepj_module) {
|
||||
return;
|
||||
}
|
||||
|
||||
// both limiters have the same shape, only the millisecond target xx differs
|
||||
// (0Ch for the 12 ms limiter, 10h for the 16 ms one):
|
||||
//
|
||||
// 48 83 F8 xx: cmp rax, xx; compare elapsed frame time with the target
|
||||
// 73 10: jae +10h; skip the wait once the target has elapsed
|
||||
// B9 xx 00 00 00: mov ecx, xx; load the target
|
||||
// 48 2B C8: sub rcx, rax; calculate the remaining wait time
|
||||
// 74 06: je +6h; skip the following six-byte Sleep call if no wait remains
|
||||
//
|
||||
// changing jae (73h) to jmp (EBh) makes each block always take its existing
|
||||
// skip path, which bypasses only the associated Sleep call and leaves the 10h
|
||||
// branch displacement and every other wait untouched.
|
||||
const auto limiter_12ms_disabled = replace_pattern(
|
||||
sharepj_module,
|
||||
"4883F80C7310B90C000000482BC87406",
|
||||
"????????EB??????????????????????", 0, 0);
|
||||
const auto limiter_16ms_disabled = replace_pattern(
|
||||
sharepj_module,
|
||||
"4883F8107310B910000000482BC87406",
|
||||
"????????EB??????????????????????", 0, 0);
|
||||
|
||||
if (!limiter_12ms_disabled || !limiter_16ms_disabled) {
|
||||
log_fatal(
|
||||
"gitadora",
|
||||
"failed to disable libshare-pj mainloop frame limiter (-gdnoframelimiter), ensure patch is not already applied");
|
||||
return;
|
||||
}
|
||||
|
||||
log_info("gitadora", "successfully disabled libshare-pj mainloop frame limiter (-gdnoframelimiter)");
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -271,9 +315,21 @@ namespace games::gitadora {
|
||||
break;
|
||||
case 2:
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
if (D3D9_ADAPTER.has_value()) {
|
||||
log_fatal(
|
||||
"gitadora",
|
||||
"arena model: 2-window mode is not supported in fullscreen, choose 1 or 4");
|
||||
"arena model: fullscreen two-window mode cannot use -monitor; "
|
||||
"use -w for borderless windows instead");
|
||||
}
|
||||
ARENA_TWO_HEAD_EXCLUSIVE = true;
|
||||
log_info(
|
||||
"gitadora",
|
||||
"arena model: native two-head fullscreen adapter-group mode "
|
||||
"(MAIN + SMALL; LEFT/RIGHT are virtual)");
|
||||
} else {
|
||||
log_info(
|
||||
"gitadora",
|
||||
"arena model: two-window mode uses windowed rendering");
|
||||
}
|
||||
log_info("gitadora", "arena model: two-window mode");
|
||||
GRAPHICS_GITADORA_HIDE_SIDE_WINDOWS = true;
|
||||
@@ -299,10 +355,18 @@ namespace games::gitadora {
|
||||
static decltype(QueryDisplayConfig) *QueryDisplayConfig_orig = nullptr;
|
||||
static decltype(DisplayConfigGetDeviceInfo) *DisplayConfigGetDeviceInfo_orig = nullptr;
|
||||
|
||||
// cached primary real monitor: its path + source/target mode entries.
|
||||
// modeInfoIdx values on the path are renumbered to 0 / 1 so the cache is self-contained.
|
||||
// Cached real monitor paths and their source/target mode entries. modeInfoIdx values are
|
||||
// renumbered so the cache is self-contained. The normal single-head emulation keeps only
|
||||
// MAIN; the two-head adapter-group mode keeps MAIN and SMALL as real heads.
|
||||
static DISPLAYCONFIG_PATH_INFO real_primary_path = {};
|
||||
static DISPLAYCONFIG_MODE_INFO real_primary_modes[2] = {}; // [0]=source, [1]=target
|
||||
static DISPLAYCONFIG_PATH_INFO real_small_path = {};
|
||||
static DISPLAYCONFIG_MODE_INFO real_small_modes[2] = {}; // [0]=source, [1]=target
|
||||
static bool real_small_path_available = false;
|
||||
|
||||
static bool is_two_head_exclusive() {
|
||||
return ARENA_TWO_HEAD_EXCLUSIVE && !GRAPHICS_WINDOWED;
|
||||
}
|
||||
|
||||
// fake monitors appended after the real ones. the game classifies monitors
|
||||
// by outputTechnology + connectorInstance:
|
||||
@@ -326,12 +390,31 @@ namespace games::gitadora {
|
||||
// adapter to a swap chain role via its DisplayConfig connector instance,
|
||||
// so the entries here must be listed in the same order the wrapper enumerates
|
||||
// them: id=1 -> adapter 1 -> left, id=2 -> adapter 2 -> right, id=3 -> adapter 3 -> small.
|
||||
static constexpr FakeMonitor FAKE_MONITORS[] = {
|
||||
// Normal single-head emulation: every non-MAIN role is fake.
|
||||
static constexpr FakeMonitor FAKE_MONITORS_SINGLE_HEAD[] = {
|
||||
{ 1, 1080, 1920, -100000, -100000, 0 }, // left (DP connector instance 0)
|
||||
{ 2, 1080, 1920, -200000, -200000, 1 }, // right (DP connector instance 1)
|
||||
{ 3, 800, 1280, -300000, -300000, 2 }, // small (DP connector instance 2, touch)
|
||||
};
|
||||
static constexpr UINT32 FAKE_MONITOR_COUNT = static_cast<UINT32>(std::size(FAKE_MONITORS));
|
||||
|
||||
// Two-head fullscreen: adapters 0/1 are the real MAIN/SMALL group heads.
|
||||
// Keep only LEFT/RIGHT fake, with their adapter ids matching the D3D9 wrapper.
|
||||
static constexpr FakeMonitor FAKE_MONITORS_TWO_HEAD[] = {
|
||||
{ 2, 1080, 1920, -200000, -200000, 0 }, // left (DP connector instance 0)
|
||||
{ 3, 1080, 1920, -300000, -300000, 1 }, // right (DP connector instance 1)
|
||||
};
|
||||
|
||||
static std::span<const FakeMonitor> get_fake_monitors() {
|
||||
if (is_two_head_exclusive()) {
|
||||
return FAKE_MONITORS_TWO_HEAD;
|
||||
}
|
||||
|
||||
return FAKE_MONITORS_SINGLE_HEAD;
|
||||
}
|
||||
|
||||
static UINT32 real_monitor_count() {
|
||||
return is_two_head_exclusive() ? 2 : 1;
|
||||
}
|
||||
|
||||
// call QueryDisplayConfig once, keep only the primary monitor's path and its
|
||||
// two referenced modes (source + target). modeInfoIdx values are rewritten to
|
||||
@@ -369,14 +452,56 @@ namespace games::gitadora {
|
||||
log_fatal("gitadora", "cache_primary_monitor_info: no primary monitor found");
|
||||
}
|
||||
|
||||
if (primary->targetInfo.modeInfoIdx >= all_modes.size()) {
|
||||
log_fatal("gitadora", "cache_primary_monitor_info: primary target mode is missing");
|
||||
}
|
||||
|
||||
real_primary_modes[0] = all_modes[primary->sourceInfo.modeInfoIdx];
|
||||
real_primary_modes[1] = all_modes[primary->targetInfo.modeInfoIdx];
|
||||
real_primary_path = *primary;
|
||||
real_primary_path.sourceInfo.modeInfoIdx = 0;
|
||||
real_primary_path.targetInfo.modeInfoIdx = 1;
|
||||
|
||||
real_small_path_available = false;
|
||||
if (is_two_head_exclusive()) {
|
||||
if (all_paths.size() != 2) {
|
||||
log_fatal(
|
||||
"gitadora",
|
||||
"two-head fullscreen mode requires exactly two active display paths, found {}",
|
||||
all_paths.size());
|
||||
}
|
||||
|
||||
const auto secondary = std::find_if(all_paths.begin(), all_paths.end(),
|
||||
[&](const auto &p) {
|
||||
return &p != &*primary;
|
||||
});
|
||||
if (secondary == all_paths.end() ||
|
||||
secondary->sourceInfo.modeInfoIdx >= all_modes.size() ||
|
||||
secondary->targetInfo.modeInfoIdx >= all_modes.size()) {
|
||||
log_fatal("gitadora", "could not identify the physical SMALL monitor");
|
||||
}
|
||||
const LUID primary_adapter = primary->sourceInfo.adapterId;
|
||||
const LUID secondary_adapter = secondary->sourceInfo.adapterId;
|
||||
const bool same_adapter = primary_adapter.HighPart == secondary_adapter.HighPart &&
|
||||
primary_adapter.LowPart == secondary_adapter.LowPart;
|
||||
if (!same_adapter) {
|
||||
log_fatal(
|
||||
"gitadora",
|
||||
"MAIN and SMALL must use the same display adapter");
|
||||
}
|
||||
|
||||
real_small_modes[0] = all_modes[secondary->sourceInfo.modeInfoIdx];
|
||||
real_small_modes[1] = all_modes[secondary->targetInfo.modeInfoIdx];
|
||||
real_small_path = *secondary;
|
||||
real_small_path.sourceInfo.modeInfoIdx = 2;
|
||||
real_small_path.targetInfo.modeInfoIdx = 3;
|
||||
real_small_path_available = true;
|
||||
|
||||
log_info("gitadora", "cache_primary_monitor_info: cached real MAIN and SMALL monitors");
|
||||
} else {
|
||||
log_info("gitadora", "cache_primary_monitor_info: cached primary monitor");
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
LONG
|
||||
@@ -390,33 +515,36 @@ namespace games::gitadora {
|
||||
static std::once_flag populate_once;
|
||||
std::call_once(populate_once, cache_primary_monitor_info);
|
||||
|
||||
// always report exactly 1 real + N fake monitors
|
||||
*pNumPathArrayElements = 1 + FAKE_MONITOR_COUNT;
|
||||
*pNumModeInfoArrayElements = 2 + FAKE_MONITOR_COUNT * 2;
|
||||
const auto fake_monitors = get_fake_monitors();
|
||||
const auto fake_count = static_cast<UINT32>(fake_monitors.size());
|
||||
const UINT32 real_count = real_monitor_count();
|
||||
*pNumPathArrayElements = real_count + fake_count;
|
||||
*pNumModeInfoArrayElements = (real_count + fake_count) * 2;
|
||||
|
||||
log_info(
|
||||
"gitadora",
|
||||
"GetDisplayConfigBufferSizes: 1 real path + {} fake monitor(s)",
|
||||
FAKE_MONITOR_COUNT);
|
||||
"GetDisplayConfigBufferSizes: {} real path(s) + {} fake monitor(s)",
|
||||
real_count,
|
||||
fake_count);
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
// write fake monitor i into the caller's path/mode arrays. layout (single-monitor
|
||||
// assumption): index 0 in both arrays holds the cached primary real monitor, so
|
||||
// fake i occupies path slot (1 + i) and mode slots (2 + i*2) / (2 + i*2 + 1).
|
||||
// Write a fake monitor after the real paths. The cache is packed as [source, target]
|
||||
// mode pairs, so each path consumes two mode entries.
|
||||
static void insert_fake_monitor(
|
||||
DISPLAYCONFIG_PATH_INFO *paths,
|
||||
DISPLAYCONFIG_MODE_INFO *modes,
|
||||
UINT32 i)
|
||||
const FakeMonitor &m,
|
||||
UINT32 path_index,
|
||||
UINT32 source_mode_index)
|
||||
{
|
||||
const FakeMonitor &m = FAKE_MONITORS[i];
|
||||
const UINT32 src_idx = 2 + i * 2;
|
||||
const UINT32 src_idx = source_mode_index;
|
||||
const UINT32 tgt_idx = src_idx + 1;
|
||||
const LUID adapter_id { .LowPart = static_cast<DWORD>(-m.id), .HighPart = -m.id };
|
||||
const UINT32 uid = static_cast<UINT32>(-m.id);
|
||||
|
||||
paths[1 + i] = {
|
||||
paths[path_index] = {
|
||||
.sourceInfo = {
|
||||
.adapterId = adapter_id,
|
||||
.id = uid,
|
||||
@@ -474,21 +602,35 @@ namespace games::gitadora {
|
||||
DISPLAYCONFIG_MODE_INFO* modeInfoArray,
|
||||
DISPLAYCONFIG_TOPOLOGY_ID* currentTopologyId)
|
||||
{
|
||||
// copy cached primary real monitor into caller buffers at index 0
|
||||
// Copy cached real monitor paths into the caller buffers.
|
||||
pathArray[0] = real_primary_path;
|
||||
modeInfoArray[0] = real_primary_modes[0];
|
||||
modeInfoArray[1] = real_primary_modes[1];
|
||||
*numPathArrayElements = 1 + FAKE_MONITOR_COUNT;
|
||||
*numModeInfoArrayElements = 2 + FAKE_MONITOR_COUNT * 2;
|
||||
if (is_two_head_exclusive() && real_small_path_available) {
|
||||
pathArray[1] = real_small_path;
|
||||
modeInfoArray[2] = real_small_modes[0];
|
||||
modeInfoArray[3] = real_small_modes[1];
|
||||
}
|
||||
|
||||
const auto fake_monitors = get_fake_monitors();
|
||||
const auto fake_count = static_cast<UINT32>(fake_monitors.size());
|
||||
const UINT32 real_count = real_monitor_count();
|
||||
*numPathArrayElements = real_count + fake_count;
|
||||
*numModeInfoArrayElements = (real_count + fake_count) * 2;
|
||||
if (currentTopologyId != nullptr) {
|
||||
*currentTopologyId = DISPLAYCONFIG_TOPOLOGY_EXTEND;
|
||||
}
|
||||
|
||||
log_misc("gitadora", "QueryDisplayConfig returning fake monitor paths and modes");
|
||||
|
||||
// append fake monitors after the real one
|
||||
for (UINT32 i = 0; i < FAKE_MONITOR_COUNT; i++) {
|
||||
insert_fake_monitor(pathArray, modeInfoArray, i);
|
||||
// Append fake monitors after the real path(s).
|
||||
for (UINT32 i = 0; i < fake_count; i++) {
|
||||
insert_fake_monitor(
|
||||
pathArray,
|
||||
modeInfoArray,
|
||||
fake_monitors[i],
|
||||
real_count + i,
|
||||
real_count * 2 + i * 2);
|
||||
}
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
@@ -519,7 +661,8 @@ namespace games::gitadora {
|
||||
const auto targetName = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
|
||||
const LONG fake_id = -id;
|
||||
UINT32 conn_inst = 0xff;
|
||||
for (const auto& f : FAKE_MONITORS) {
|
||||
const auto fake_monitors = get_fake_monitors();
|
||||
for (const auto &f : fake_monitors) {
|
||||
if (f.id == fake_id) {
|
||||
conn_inst = f.connector_instance;
|
||||
break;
|
||||
@@ -542,18 +685,27 @@ namespace games::gitadora {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// override the cached primary real monitor target info to look like HDMI/0
|
||||
// Override MAIN to HDMI/0 and retag the real second head as the DP/2 SMALL
|
||||
// display expected by the cabinet software in two-head fullscreen mode.
|
||||
const auto targetName = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
|
||||
const auto& target = real_primary_path.targetInfo;
|
||||
if (target.id == targetName->header.id &&
|
||||
const auto target_matches = [&](const DISPLAYCONFIG_PATH_TARGET_INFO &target) {
|
||||
return target.id == targetName->header.id &&
|
||||
target.adapterId.HighPart == targetName->header.adapterId.HighPart &&
|
||||
target.adapterId.LowPart == targetName->header.adapterId.LowPart)
|
||||
{
|
||||
target.adapterId.LowPart == targetName->header.adapterId.LowPart;
|
||||
};
|
||||
if (target_matches(real_primary_path.targetInfo)) {
|
||||
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
|
||||
targetName->connectorInstance = 0;
|
||||
log_info("gitadora",
|
||||
"overriding primary monitor (id={}) to pretend to be HDMI",
|
||||
targetName->header.id);
|
||||
} else if (is_two_head_exclusive() && real_small_path_available &&
|
||||
target_matches(real_small_path.targetInfo)) {
|
||||
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL;
|
||||
targetName->connectorInstance = 2;
|
||||
log_info("gitadora",
|
||||
"overriding secondary monitor (id={}) to pretend to be SMALL DP/2",
|
||||
targetName->header.id);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -576,6 +728,12 @@ namespace games::gitadora {
|
||||
HMODULE system_module = libutils::try_module("libsystem.dll");
|
||||
|
||||
// patches
|
||||
#ifdef SPICE64
|
||||
if (DISABLE_FRAME_LIMITER && !is_arena_model()) {
|
||||
disable_mainloop_frame_limiter(sharepj_module);
|
||||
}
|
||||
#endif
|
||||
|
||||
detour::inline_hook((void *) eam_network_detected_ip_change, libutils::try_proc(
|
||||
sharepj_module, "eam_network_detected_ip_change"));
|
||||
detour::inline_hook((void *) eam_network_settings_conflict, libutils::try_proc(
|
||||
@@ -651,7 +809,8 @@ namespace games::gitadora {
|
||||
hooks::audio::INJECT_FAKE_REALTEK_AUDIO = true;
|
||||
}
|
||||
|
||||
// monitor/touch hooks (windowed or full screen)
|
||||
// Single-window mode needs touch injection for its overlay. Two-head fullscreen
|
||||
// mode uses the real SMALL output, so it only needs the display-topology shim.
|
||||
if (GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||
// enable touch hook for subscreen overlay
|
||||
const auto native_touch_ready = !wintouchemu::FORCE &&
|
||||
@@ -661,12 +820,17 @@ namespace games::gitadora {
|
||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
#if !SPICE_XP
|
||||
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
// monitor hook: always pretend to have 1 primary real monitor + 3 fake monitors
|
||||
// (LEFT / RIGHT / SMALL) so the game accepts the arena cab topology
|
||||
if (!GRAPHICS_WINDOWED &&
|
||||
(GRAPHICS_PREVENT_SECONDARY_WINDOWS || ARENA_TWO_HEAD_EXCLUSIVE)) {
|
||||
if (ARENA_TWO_HEAD_EXCLUSIVE) {
|
||||
log_info(
|
||||
"gitadora",
|
||||
"exposing physical MAIN/SMALL and virtual LEFT/RIGHT");
|
||||
}
|
||||
GetDisplayConfigBufferSizes_orig =
|
||||
detour::iat_try("GetDisplayConfigBufferSizes",
|
||||
GetDisplayConfigBufferSizes_hook, avs::game::DLL_INSTANCE);
|
||||
@@ -678,8 +842,6 @@ namespace games::gitadora {
|
||||
DisplayConfigGetDeviceInfo_hook, avs::game::DLL_INSTANCE);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// window patch
|
||||
|
||||
@@ -13,12 +13,14 @@ namespace games::gitadora {
|
||||
|
||||
// settings
|
||||
extern bool TWOCHANNEL;
|
||||
extern bool DISABLE_FRAME_LIMITER;
|
||||
extern std::optional<unsigned int> CAB_TYPE;
|
||||
extern bool P1_LEFTY;
|
||||
extern bool P2_LEFTY;
|
||||
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||
extern std::optional<socd::SocdAlgorithm> PICK_ALGO;
|
||||
extern std::optional<uint8_t> ARENA_WINDOW_COUNT;
|
||||
extern bool ARENA_TWO_HEAD_EXCLUSIVE;
|
||||
extern std::optional<std::string> ASIO_DRIVER;
|
||||
extern bool ALLOW_REALTEK_AUDIO;
|
||||
|
||||
|
||||
@@ -534,7 +534,7 @@ namespace games::iidx {
|
||||
if (mediaTypePointer && mediaTypePointer->IsString()) {
|
||||
std::string mediaType = mediaTypePointer->GetString();
|
||||
if (mediaType.length() > 0) {
|
||||
camera->m_selectedMediaTypeDescription = mediaType;
|
||||
camera->SetSelectedMediaTypeDescription(mediaType);
|
||||
camera->m_useAutoMediaType = false;
|
||||
} else {
|
||||
camera->m_useAutoMediaType = true;
|
||||
@@ -547,7 +547,7 @@ namespace games::iidx {
|
||||
std::string drawModeString = drawModePointer->GetString();
|
||||
for (int j = 0; j < DRAW_MODE_SIZE; j++) {
|
||||
if (DRAW_MODE_LABELS[j].compare(drawModeString) == 0) {
|
||||
camera->m_drawMode = (LocalCameraDrawMode) j;
|
||||
camera->m_drawMode.store((LocalCameraDrawMode) j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -556,12 +556,12 @@ namespace games::iidx {
|
||||
// Flip
|
||||
auto flipHorizontalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipHorizontal").Get(doc);
|
||||
if (flipHorizontalPointer && flipHorizontalPointer->IsBool()) {
|
||||
camera->m_flipHorizontal = flipHorizontalPointer->GetBool();
|
||||
camera->m_flipHorizontal.store(flipHorizontalPointer->GetBool());
|
||||
}
|
||||
|
||||
auto flipVerticalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipVertical").Get(doc);
|
||||
if (flipVerticalPointer && flipVerticalPointer->IsBool()) {
|
||||
camera->m_flipVertical = flipVerticalPointer->GetBool();
|
||||
camera->m_flipVertical.store(flipVerticalPointer->GetBool());
|
||||
}
|
||||
|
||||
// Allow manual control
|
||||
@@ -625,15 +625,17 @@ namespace games::iidx {
|
||||
if (camera->m_useAutoMediaType) {
|
||||
rapidjson::Pointer(root + "MediaType").Set(doc, "");
|
||||
} else {
|
||||
rapidjson::Pointer(root + "MediaType").Set(doc, camera->m_selectedMediaTypeDescription);
|
||||
const auto mediaTypeDescription = camera->GetSelectedMediaTypeDescription();
|
||||
rapidjson::Pointer(root + "MediaType").Set(doc, mediaTypeDescription);
|
||||
}
|
||||
|
||||
// Draw Mode
|
||||
rapidjson::Pointer(root + "DrawMode").Set(doc, DRAW_MODE_LABELS[camera->m_drawMode]);
|
||||
const auto drawMode = camera->m_drawMode.load();
|
||||
rapidjson::Pointer(root + "DrawMode").Set(doc, DRAW_MODE_LABELS[drawMode]);
|
||||
|
||||
// Flip
|
||||
rapidjson::Pointer(root + "FlipHorizontal").Set(doc, camera->m_flipHorizontal);
|
||||
rapidjson::Pointer(root + "FlipVertical").Set(doc, camera->m_flipVertical);
|
||||
rapidjson::Pointer(root + "FlipHorizontal").Set(doc, camera->m_flipHorizontal.load());
|
||||
rapidjson::Pointer(root + "FlipVertical").Set(doc, camera->m_flipVertical.load());
|
||||
|
||||
// Manual control
|
||||
rapidjson::Pointer(root + "AllowManualControl").Set(doc, camera->m_allowManualControl);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
||||
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include <atomic>
|
||||
#include <d3d9.h>
|
||||
#include <dxva2api.h>
|
||||
#include <mutex>
|
||||
@@ -66,6 +67,8 @@ extern std::string CAMERA_CONTROL_LABELS[];
|
||||
extern std::string DRAW_MODE_LABELS[];
|
||||
|
||||
namespace games::iidx {
|
||||
class IIDXCameraSourceReaderCallback;
|
||||
|
||||
namespace Camera {
|
||||
struct PlayVideoCamera {
|
||||
IDirect3DTexture9** d3d9_texture(const uintptr_t offset) {
|
||||
@@ -87,7 +90,7 @@ namespace games::iidx {
|
||||
|
||||
class IIDXLocalCamera {
|
||||
protected:
|
||||
virtual ~IIDXLocalCamera() {};
|
||||
virtual ~IIDXLocalCamera();
|
||||
|
||||
LONG m_nRefCount;
|
||||
CRITICAL_SECTION m_critsec;
|
||||
@@ -101,9 +104,14 @@ namespace games::iidx {
|
||||
// For reading frames from Camera
|
||||
IMFMediaSource *m_pSource = nullptr;
|
||||
IMFSourceReader *m_pSourceReader = nullptr;
|
||||
IMFSourceReaderEx *m_pSourceReaderEx = nullptr;
|
||||
IIDXCameraSourceReaderCallback *m_pSourceReaderCallback = nullptr;
|
||||
std::mutex m_mediaTypeMutex;
|
||||
IMFMediaType *m_pendingMediaType = nullptr;
|
||||
int m_selectedMediaTypeIndex = 0;
|
||||
std::string m_selectedMediaTypeDescription = "";
|
||||
|
||||
// Camera Format information
|
||||
double m_frameRate = 0;
|
||||
LONG m_cameraWidth;
|
||||
LONG m_cameraHeight;
|
||||
|
||||
@@ -129,6 +137,12 @@ namespace games::iidx {
|
||||
LPDIRECT3DTEXTURE9 m_conversionTexture = nullptr;
|
||||
IDirect3DSurface9 *m_pConversionSurf = nullptr;
|
||||
|
||||
// upload surface for decoded camera frames returned in system memory
|
||||
IDirect3DSurface9 *m_pDecodedSurf = nullptr;
|
||||
GUID m_decodedSubtype = GUID_NULL;
|
||||
GUID m_outputSubtype = GUID_NULL;
|
||||
bool m_drawErrorLogged = false;
|
||||
|
||||
// Texture for custom transform (e.g. horizontal flip)
|
||||
LPDIRECT3DTEXTURE9 m_transformTexture = nullptr;
|
||||
IDirect3DSurface9 *m_pTransformSurf = nullptr;
|
||||
@@ -153,21 +167,19 @@ namespace games::iidx {
|
||||
BOOL m_initialized = false;
|
||||
|
||||
// True if all the setup steps succeeded
|
||||
BOOL m_active = false;
|
||||
std::atomic_bool m_active = false;
|
||||
|
||||
// Media type select
|
||||
std::vector<MediaTypeInfo> m_mediaTypeInfos = {};
|
||||
int m_selectedMediaTypeIndex = 0;
|
||||
bool m_useAutoMediaType = true;
|
||||
IMFMediaType *m_pAutoMediaType = nullptr;
|
||||
std::string m_selectedMediaTypeDescription = "";
|
||||
bool m_allowManualControl = false;
|
||||
|
||||
LocalCameraDrawMode m_drawMode = DrawModeCrop4_3;
|
||||
std::atomic<LocalCameraDrawMode> m_drawMode = DrawModeCrop4_3;
|
||||
|
||||
// Render processing
|
||||
bool m_flipHorizontal = false;
|
||||
bool m_flipVertical = false;
|
||||
std::atomic_bool m_flipHorizontal = false;
|
||||
std::atomic_bool m_flipVertical = false;
|
||||
|
||||
IIDXLocalCamera(
|
||||
std::string name,
|
||||
@@ -184,11 +196,14 @@ namespace games::iidx {
|
||||
HRESULT GetCameraControlProp(int index, CameraControlProp *pProp);
|
||||
HRESULT SetCameraControlProp(int index, long value, long flags);
|
||||
HRESULT ResetCameraControlProps();
|
||||
HRESULT FlushDrawCommands();
|
||||
std::string GetName();
|
||||
std::string GetFriendlyName();
|
||||
std::string GetSymLink();
|
||||
int GetSelectedMediaTypeIndex();
|
||||
std::string GetSelectedMediaTypeDescription();
|
||||
void SetSelectedMediaTypeDescription(const std::string &description);
|
||||
HRESULT ChangeMediaType(IMFMediaType *pType);
|
||||
void RequestMediaType(IMFMediaType *pType);
|
||||
HRESULT StartCapture();
|
||||
void UpdateDrawRect();
|
||||
|
||||
@@ -197,12 +212,22 @@ namespace games::iidx {
|
||||
void CreateThread();
|
||||
MediaTypeInfo GetMediaTypeInfo(IMFMediaType *pType);
|
||||
std::string GetVideoFormatName(GUID subtype);
|
||||
HRESULT TryMediaType(IMFMediaType *pType, UINT32 *pBestWidth, double *pBestFrameRate);
|
||||
static bool CompareMediaTypes(const MediaTypeInfo &a, const MediaTypeInfo &b);
|
||||
bool MatchesPreferredAspect(const MediaTypeInfo &info) const;
|
||||
static bool IsBetterAutoType(const MediaTypeInfo &candidate, const MediaTypeInfo ¤t);
|
||||
IMFMediaType *FindBestNativeAutoType(bool requirePreferredAspect) const;
|
||||
IMFMediaType *FindBestAutoType(const GUID &subtype, bool requirePreferredAspect) const;
|
||||
HRESULT ValidateMediaType(IMFMediaType *pType);
|
||||
HRESULT InitTargetTexture();
|
||||
HRESULT EnsureTransformTextures();
|
||||
HRESULT InitCameraControl();
|
||||
void SetSelectedMediaType(int index, const std::string &description);
|
||||
bool HasPendingMediaType();
|
||||
HRESULT ApplyPendingMediaType();
|
||||
HRESULT UploadDecodedSample(IMFMediaBuffer *pSrcBuffer);
|
||||
HRESULT DrawSample(IMFMediaBuffer *pSrcBuffer);
|
||||
HRESULT ReadSample();
|
||||
LPDIRECT3DTEXTURE9 Render();
|
||||
HRESULT Render();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace games::iidx {
|
||||
_In_ UINT32 cInitialSize
|
||||
);
|
||||
|
||||
typedef HRESULT (__stdcall * MFCreateMediaType_t)(
|
||||
_Out_ IMFMediaType** ppMFType
|
||||
);
|
||||
|
||||
typedef HRESULT (__stdcall * MFEnumDeviceSources_t)(
|
||||
_In_ IMFAttributes* pAttributes,
|
||||
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
||||
@@ -35,6 +39,7 @@ namespace games::iidx {
|
||||
);
|
||||
|
||||
static MFCreateAttributes_t MFCreateAttributes = nullptr;
|
||||
static MFCreateMediaType_t MFCreateMediaType = nullptr;
|
||||
static MFEnumDeviceSources_t MFEnumDeviceSources = nullptr;
|
||||
static MFCreateSourceReaderFromMediaSource_t MFCreateSourceReaderFromMediaSource = nullptr;
|
||||
static MFGetService_t MFGetService = nullptr;
|
||||
@@ -66,6 +71,12 @@ namespace games::iidx {
|
||||
log_fatal("mf_wrappers", "MFCreateAttributes failed to hook");
|
||||
}
|
||||
|
||||
MFCreateMediaType = (MFCreateMediaType_t)
|
||||
libutils::get_proc(mfplat_dll, "MFCreateMediaType");
|
||||
if (!MFCreateMediaType) {
|
||||
log_fatal("mf_wrappers", "MFCreateMediaType failed to hook");
|
||||
}
|
||||
|
||||
MFEnumDeviceSources = (MFEnumDeviceSources_t)
|
||||
libutils::get_proc(mf_dll, "MFEnumDeviceSources");
|
||||
if (!MFEnumDeviceSources) {
|
||||
@@ -92,6 +103,11 @@ namespace games::iidx {
|
||||
return MFCreateAttributes(ppMFAttributes, cInitialSize);
|
||||
}
|
||||
|
||||
HRESULT WrappedMFCreateMediaType (
|
||||
_Out_ IMFMediaType** ppMFType) {
|
||||
return MFCreateMediaType(ppMFType);
|
||||
}
|
||||
|
||||
HRESULT WrappedMFEnumDeviceSources (
|
||||
_In_ IMFAttributes* pAttributes,
|
||||
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace games::iidx {
|
||||
_Out_ IMFAttributes** ppMFAttributes,
|
||||
_In_ UINT32 cInitialSize);
|
||||
|
||||
HRESULT WrappedMFCreateMediaType (
|
||||
_Out_ IMFMediaType** ppMFType);
|
||||
|
||||
HRESULT WrappedMFEnumDeviceSources (
|
||||
_In_ IMFAttributes* pAttributes,
|
||||
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "poke.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "windows.h"
|
||||
@@ -11,7 +12,6 @@
|
||||
#include "touch/native/inject.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
namespace games::iidx::poke {
|
||||
|
||||
@@ -125,8 +125,6 @@ namespace games::iidx::poke {
|
||||
// create new thread
|
||||
THREAD_RUNNING = true;
|
||||
THREAD = new std::thread([] {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// log
|
||||
log_info("poke", "enabled");
|
||||
|
||||
@@ -198,7 +196,7 @@ namespace games::iidx::poke {
|
||||
}
|
||||
|
||||
// slow down
|
||||
timer.sleep(50);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
|
||||
if (!touch_points.empty()) {
|
||||
|
||||
@@ -133,6 +133,7 @@ namespace games {
|
||||
buttons_help.insert({ jb, jb::get_buttons_help() });
|
||||
lights.insert({ jb, jb::get_lights() });
|
||||
file_hints[jb].push_back({"jubeat.dll"});
|
||||
file_hints[jb].push_back({"jubeat2019.dll"});
|
||||
|
||||
// mga
|
||||
const std::string mga("Metal Gear");
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "games/io.h"
|
||||
#include "games/jb/jb_touch.h"
|
||||
#include "io.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace games::jb {
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
std::array<uint8_t, 0x100> data;
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2;
|
||||
|
||||
struct AIO_NMGR_IOB2_VTABLE {
|
||||
std::array<void *, 10> unused;
|
||||
void (__fastcall *begin_manage)(AIO_NMGR_IOB2 *node_mgr);
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
AIO_NMGR_IOB2_VTABLE *vtable;
|
||||
std::array<uint8_t, 0x78> data;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_T44 {
|
||||
std::array<uint8_t, 0x80> data;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_T44_DEVSTATUS {
|
||||
std::array<uint8_t, 0x140> data;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
uint8_t data;
|
||||
};
|
||||
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0x80);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_T44_DEVSTATUS) == 0x140);
|
||||
|
||||
using aioIob2Bi2xT44_Create_t = AIO_IOB2_BI2X_T44 *(__fastcall *)(AIO_NMGR_IOB2 *node_mgr,
|
||||
uint32_t device_id);
|
||||
using aioIob2Bi2xT44_GetDeviceStatus_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||
AIO_IOB2_BI2X_T44_DEVSTATUS *status);
|
||||
using aioIob2Bi2xT44_IoReset_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node, uint32_t reset);
|
||||
using aioIob2Bi2xT44_SetWatchDogTimer_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node, uint8_t count);
|
||||
using aioIob2Bi2xT44_ControlCoinBlocker_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||
uint32_t slot, bool open);
|
||||
using aioIob2Bi2xT44_AddCounter_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||
uint32_t counter, uint32_t count);
|
||||
using aioIob2Bi2xT44_SetIccrLed_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node, uint32_t color);
|
||||
using aioIob2Bi2xT44_SetTapeLedData_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||
uint32_t tape, const void *data);
|
||||
using aioIob2Bi2x_OpenSciUsbCdc_t = AIO_SCI_COMM *(__fastcall *)(uint32_t serial_number);
|
||||
using aioIob2Bi2x_CreateWriteFirmContext_t = AIO_IOB2_BI2X_WRFIRM *(__fastcall *)(
|
||||
uint32_t serial_number, uint32_t iob_mask);
|
||||
using aioIob2Bi2x_DestroyWriteFirmContext_t = void (__fastcall *)(AIO_IOB2_BI2X_WRFIRM *context);
|
||||
using aioIob2Bi2x_WriteFirmGetState_t = int32_t (__fastcall *)(AIO_IOB2_BI2X_WRFIRM *context);
|
||||
using aioIob2Bi2x_WriteFirmIsCompleted_t = bool (__fastcall *)(int32_t state);
|
||||
using aioIob2Bi2x_WriteFirmIsError_t = bool (__fastcall *)(int32_t state);
|
||||
using aioNMgrIob2_Create_t = AIO_NMGR_IOB2 *(__fastcall *)(AIO_SCI_COMM *sci, uint32_t mode);
|
||||
using aioSci_Destroy_t = void (__fastcall *)(AIO_SCI_COMM *sci);
|
||||
using aioNodeMgr_Destroy_t = void (__fastcall *)(AIO_NMGR_IOB2 *node_mgr);
|
||||
using aioNodeCtl_Destroy_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node);
|
||||
using aioNodeCtl_UpdateDevicesStatus_t = void (__fastcall *)();
|
||||
|
||||
static aioIob2Bi2xT44_Create_t aioIob2Bi2xT44_Create_orig = nullptr;
|
||||
static aioIob2Bi2xT44_GetDeviceStatus_t aioIob2Bi2xT44_GetDeviceStatus_orig = nullptr;
|
||||
static aioIob2Bi2xT44_IoReset_t aioIob2Bi2xT44_IoReset_orig = nullptr;
|
||||
static aioIob2Bi2xT44_SetWatchDogTimer_t aioIob2Bi2xT44_SetWatchDogTimer_orig = nullptr;
|
||||
static aioIob2Bi2xT44_ControlCoinBlocker_t aioIob2Bi2xT44_ControlCoinBlocker_orig = nullptr;
|
||||
static aioIob2Bi2xT44_AddCounter_t aioIob2Bi2xT44_AddCounter_orig = nullptr;
|
||||
static aioIob2Bi2xT44_SetIccrLed_t aioIob2Bi2xT44_SetIccrLed_orig = nullptr;
|
||||
static aioIob2Bi2xT44_SetTapeLedData_t aioIob2Bi2xT44_SetTapeLedData_orig = nullptr;
|
||||
static aioIob2Bi2x_OpenSciUsbCdc_t aioIob2Bi2x_OpenSciUsbCdc_orig = nullptr;
|
||||
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
|
||||
static aioIob2Bi2x_DestroyWriteFirmContext_t aioIob2Bi2x_DestroyWriteFirmContext_orig = nullptr;
|
||||
static aioIob2Bi2x_WriteFirmGetState_t aioIob2Bi2x_WriteFirmGetState_orig = nullptr;
|
||||
static aioIob2Bi2x_WriteFirmIsCompleted_t aioIob2Bi2x_WriteFirmIsCompleted_orig = nullptr;
|
||||
static aioIob2Bi2x_WriteFirmIsError_t aioIob2Bi2x_WriteFirmIsError_orig = nullptr;
|
||||
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
|
||||
static aioSci_Destroy_t aioSci_Destroy_orig = nullptr;
|
||||
static aioNodeMgr_Destroy_t aioNodeMgr_Destroy_orig = nullptr;
|
||||
static aioNodeCtl_Destroy_t aioNodeCtl_Destroy_orig = nullptr;
|
||||
static aioNodeCtl_UpdateDevicesStatus_t aioNodeCtl_UpdateDevicesStatus_orig = nullptr;
|
||||
|
||||
static AIO_SCI_COMM *aio_sci_comm = nullptr;
|
||||
static AIO_NMGR_IOB2 *aio_node_mgr = nullptr;
|
||||
static AIO_IOB2_BI2X_T44 *aio_t44 = nullptr;
|
||||
static AIO_IOB2_BI2X_WRFIRM *aio_write_firm = nullptr;
|
||||
static uint8_t input_counter = 0;
|
||||
|
||||
static void __fastcall aioNMgrIob_BeginManage(AIO_NMGR_IOB2 *) {
|
||||
}
|
||||
|
||||
static AIO_NMGR_IOB2_VTABLE aio_node_mgr_vtable {
|
||||
{},
|
||||
aioNMgrIob_BeginManage,
|
||||
};
|
||||
|
||||
static AIO_SCI_COMM *__fastcall aioIob2Bi2x_OpenSciUsbCdc(uint32_t) {
|
||||
aio_sci_comm = new AIO_SCI_COMM {};
|
||||
return aio_sci_comm;
|
||||
}
|
||||
|
||||
static AIO_NMGR_IOB2 *__fastcall aioNMgrIob2_Create(AIO_SCI_COMM *sci, uint32_t mode) {
|
||||
if (sci != aio_sci_comm) {
|
||||
return aioNMgrIob2_Create_orig(sci, mode);
|
||||
}
|
||||
|
||||
aio_node_mgr = new AIO_NMGR_IOB2 {};
|
||||
aio_node_mgr->vtable = &aio_node_mgr_vtable;
|
||||
return aio_node_mgr;
|
||||
}
|
||||
|
||||
static AIO_IOB2_BI2X_T44 *__fastcall aioIob2Bi2xT44_Create(
|
||||
AIO_NMGR_IOB2 *node_mgr, uint32_t device_id) {
|
||||
if (node_mgr != aio_node_mgr) {
|
||||
return aioIob2Bi2xT44_Create_orig(node_mgr, device_id);
|
||||
}
|
||||
|
||||
log_info("jb::bi2x", "T44 node created");
|
||||
aio_t44 = new AIO_IOB2_BI2X_T44 {};
|
||||
return aio_t44;
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xT44_GetDeviceStatus(
|
||||
AIO_IOB2_BI2X_T44 *node, AIO_IOB2_BI2X_T44_DEVSTATUS *status) {
|
||||
if (node != aio_t44) {
|
||||
return aioIob2Bi2xT44_GetDeviceStatus_orig(node, status);
|
||||
}
|
||||
|
||||
RI_MGR->devices_flush_output();
|
||||
std::memset(status, 0, sizeof(*status));
|
||||
|
||||
status->data[0x00] = input_counter;
|
||||
status->data[0x03] = input_counter++;
|
||||
status->data[0x0A] = static_cast<uint8_t>(eamuse_coin_get_stock());
|
||||
|
||||
games::jb::touch_update();
|
||||
const auto touched = games::jb::touch_state();
|
||||
auto &buttons = get_buttons();
|
||||
|
||||
status->data[0x04] = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]) ? 0xFF : 0;
|
||||
status->data[0x05] = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]) ? 0xFF : 0;
|
||||
status->data[0x06] = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]) ? 0xFF : 0;
|
||||
|
||||
static constexpr size_t PANEL_ORDER[16] = {
|
||||
3, 7, 11, 15,
|
||||
2, 6, 10, 14,
|
||||
1, 5, 9, 13,
|
||||
0, 4, 8, 12,
|
||||
};
|
||||
for (size_t status_index = 0; status_index < std::size(PANEL_ORDER); status_index++) {
|
||||
const auto panel_index = PANEL_ORDER[status_index];
|
||||
if (touched[panel_index] || GameAPI::Buttons::getState(
|
||||
RI_MGR, buttons[Buttons::Button1 + panel_index])) {
|
||||
status->data[0x0F + status_index] = 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xT44_IoReset(AIO_IOB2_BI2X_T44 *node, uint32_t reset) {
|
||||
if (node != aio_t44) {
|
||||
return aioIob2Bi2xT44_IoReset_orig(node, reset);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xT44_SetWatchDogTimer(AIO_IOB2_BI2X_T44 *node, uint8_t count) {
|
||||
if (node != aio_t44) {
|
||||
return aioIob2Bi2xT44_SetWatchDogTimer_orig(node, count);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xT44_ControlCoinBlocker(
|
||||
AIO_IOB2_BI2X_T44 *node, uint32_t slot, bool open) {
|
||||
if (node != aio_t44) {
|
||||
return aioIob2Bi2xT44_ControlCoinBlocker_orig(node, slot, open);
|
||||
}
|
||||
|
||||
eamuse_coin_set_block(!open);
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xT44_AddCounter(
|
||||
AIO_IOB2_BI2X_T44 *node, uint32_t counter, uint32_t count) {
|
||||
if (node != aio_t44) {
|
||||
return aioIob2Bi2xT44_AddCounter_orig(node, counter, count);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xT44_SetIccrLed(AIO_IOB2_BI2X_T44 *node, uint32_t color) {
|
||||
if (node != aio_t44) {
|
||||
return aioIob2Bi2xT44_SetIccrLed_orig(node, color);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xT44_SetTapeLedData(
|
||||
AIO_IOB2_BI2X_T44 *node, uint32_t tape, const void *data) {
|
||||
if (node != aio_t44) {
|
||||
return aioIob2Bi2xT44_SetTapeLedData_orig(node, tape, data);
|
||||
}
|
||||
}
|
||||
|
||||
static AIO_IOB2_BI2X_WRFIRM *__fastcall aioIob2Bi2x_CreateWriteFirmContext(uint32_t, uint32_t) {
|
||||
aio_write_firm = new AIO_IOB2_BI2X_WRFIRM {};
|
||||
return aio_write_firm;
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2x_DestroyWriteFirmContext(AIO_IOB2_BI2X_WRFIRM *context) {
|
||||
if (context != aio_write_firm) {
|
||||
return aioIob2Bi2x_DestroyWriteFirmContext_orig(context);
|
||||
}
|
||||
|
||||
delete aio_write_firm;
|
||||
aio_write_firm = nullptr;
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioIob2Bi2x_WriteFirmGetState(AIO_IOB2_BI2X_WRFIRM *context) {
|
||||
if (context != aio_write_firm) {
|
||||
return aioIob2Bi2x_WriteFirmGetState_orig(context);
|
||||
}
|
||||
return 8;
|
||||
}
|
||||
|
||||
static bool __fastcall aioIob2Bi2x_WriteFirmIsCompleted(int32_t state) {
|
||||
if (aio_write_firm) {
|
||||
return true;
|
||||
}
|
||||
return aioIob2Bi2x_WriteFirmIsCompleted_orig(state);
|
||||
}
|
||||
|
||||
static bool __fastcall aioIob2Bi2x_WriteFirmIsError(int32_t state) {
|
||||
if (aio_write_firm) {
|
||||
return false;
|
||||
}
|
||||
return aioIob2Bi2x_WriteFirmIsError_orig(state);
|
||||
}
|
||||
|
||||
static void __fastcall aioSci_Destroy(AIO_SCI_COMM *sci) {
|
||||
if (sci != aio_sci_comm) {
|
||||
return aioSci_Destroy_orig(sci);
|
||||
}
|
||||
|
||||
delete aio_sci_comm;
|
||||
aio_sci_comm = nullptr;
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB2 *node_mgr) {
|
||||
if (node_mgr != aio_node_mgr) {
|
||||
return aioNodeMgr_Destroy_orig(node_mgr);
|
||||
}
|
||||
|
||||
delete aio_node_mgr;
|
||||
aio_node_mgr = nullptr;
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeCtl_Destroy(AIO_IOB2_BI2X_T44 *node) {
|
||||
if (node != aio_t44) {
|
||||
return aioNodeCtl_Destroy_orig(node);
|
||||
}
|
||||
|
||||
delete aio_t44;
|
||||
aio_t44 = nullptr;
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeCtl_UpdateDevicesStatus() {
|
||||
}
|
||||
|
||||
void bi2x_hook_init() {
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
|
||||
log_info("jb::bi2x", "initializing T44 hooks");
|
||||
|
||||
const auto libaio_iob2_video = "libaio-iob2_video.dll";
|
||||
detour::trampoline_try(libaio_iob2_video, "aioIob2Bi2xT44_Create",
|
||||
aioIob2Bi2xT44_Create, &aioIob2Bi2xT44_Create_orig);
|
||||
detour::trampoline_try(libaio_iob2_video,
|
||||
"?GetDeviceStatus@AIO_IOB2_BI2X_T44@@QEBAXAEAUDEVSTATUS@1@@Z",
|
||||
aioIob2Bi2xT44_GetDeviceStatus, &aioIob2Bi2xT44_GetDeviceStatus_orig);
|
||||
detour::trampoline_try(libaio_iob2_video, "?IoReset@AIO_IOB2_BI2X_T44@@QEAAXI@Z",
|
||||
aioIob2Bi2xT44_IoReset, &aioIob2Bi2xT44_IoReset_orig);
|
||||
detour::trampoline_try(libaio_iob2_video,
|
||||
"?SetWatchDogTimer@AIO_IOB2_BI2X_T44@@QEAAXE@Z",
|
||||
aioIob2Bi2xT44_SetWatchDogTimer, &aioIob2Bi2xT44_SetWatchDogTimer_orig);
|
||||
detour::trampoline_try(libaio_iob2_video,
|
||||
"?ControlCoinBlocker@AIO_IOB2_BI2X_T44@@QEAAXI_N@Z",
|
||||
aioIob2Bi2xT44_ControlCoinBlocker, &aioIob2Bi2xT44_ControlCoinBlocker_orig);
|
||||
detour::trampoline_try(libaio_iob2_video, "?AddCounter@AIO_IOB2_BI2X_T44@@QEAAXII@Z",
|
||||
aioIob2Bi2xT44_AddCounter, &aioIob2Bi2xT44_AddCounter_orig);
|
||||
detour::trampoline_try(libaio_iob2_video, "?SetIccrLed@AIO_IOB2_BI2X_T44@@QEAAXI@Z",
|
||||
aioIob2Bi2xT44_SetIccrLed, &aioIob2Bi2xT44_SetIccrLed_orig);
|
||||
detour::trampoline_try(libaio_iob2_video,
|
||||
"?SetTapeLedData@AIO_IOB2_BI2X_T44@@QEAAXIPEBX@Z",
|
||||
aioIob2Bi2xT44_SetTapeLedData, &aioIob2Bi2xT44_SetTapeLedData_orig);
|
||||
|
||||
const auto libaio_iob = "libaio-iob.dll";
|
||||
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_OpenSciUsbCdc",
|
||||
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
||||
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_CreateWriteFirmContext",
|
||||
aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig);
|
||||
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_DestroyWriteFirmContext",
|
||||
aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig);
|
||||
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_WriteFirmGetState",
|
||||
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
|
||||
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_WriteFirmIsCompleted",
|
||||
aioIob2Bi2x_WriteFirmIsCompleted, &aioIob2Bi2x_WriteFirmIsCompleted_orig);
|
||||
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_WriteFirmIsError",
|
||||
aioIob2Bi2x_WriteFirmIsError, &aioIob2Bi2x_WriteFirmIsError_orig);
|
||||
detour::trampoline_try(libaio_iob, "aioNMgrIob2_Create",
|
||||
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||
|
||||
const auto libaio = "libaio.dll";
|
||||
detour::trampoline_try(libaio, "aioSci_Destroy", aioSci_Destroy, &aioSci_Destroy_orig);
|
||||
detour::trampoline_try(libaio, "aioNodeMgr_Destroy",
|
||||
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
|
||||
detour::trampoline_try(libaio, "aioNodeCtl_Destroy",
|
||||
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
|
||||
detour::trampoline_try(libaio, "aioNodeCtl_UpdateDevicesStatus",
|
||||
aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
namespace games::jb {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <windows.h>
|
||||
#include <filesystem>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "bi2x_hook.h"
|
||||
#include "cfg/configurator.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/detour.h"
|
||||
@@ -10,6 +12,8 @@
|
||||
|
||||
namespace games::jb {
|
||||
|
||||
#if !SPICE64
|
||||
|
||||
// fixes "IP ADDR CHANGE" errors with unusual network setups (e.g. a VPN)
|
||||
static BOOL __stdcall network_addr_is_changed() {
|
||||
return 0;
|
||||
@@ -31,6 +35,8 @@ namespace games::jb {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
JBGame::JBGame() : Game("Jubeat") {
|
||||
}
|
||||
|
||||
@@ -60,8 +66,18 @@ namespace games::jb {
|
||||
void JBGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
#if SPICE64
|
||||
if (avs::game::DLL_NAME == "jubeat2019.dll") {
|
||||
libutils::load_library("libaio.dll");
|
||||
libutils::load_library("libaio-iob.dll");
|
||||
libutils::load_library("libaio-iob2_video.dll");
|
||||
bi2x_hook_init();
|
||||
}
|
||||
#endif
|
||||
|
||||
touch_attach();
|
||||
|
||||
#if !SPICE64
|
||||
// enable debug logging of gftools
|
||||
HMODULE gftools = libutils::try_module("gftools.dll");
|
||||
detour::inline_hook((void *) GFDbgSetReportFunc, libutils::try_proc(
|
||||
@@ -75,6 +91,8 @@ namespace games::jb {
|
||||
network, "network_get_network_check_info"));
|
||||
detour::inline_hook((void *) network_get_dhcp_result, libutils::try_proc(
|
||||
network, "network_get_dhcp_result"));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void JBGame::detach() {
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
// (6, 8) in landscape.
|
||||
#define JB_BUTTON_SIZE 160
|
||||
#define JB_MAX_BUTTON_GAP 38
|
||||
#define JB_T44_BUTTON_SIZE 224
|
||||
#define JB_T44_BUTTON_GAP 33
|
||||
|
||||
// improved and plus modes use this reach around each button. must be >= the
|
||||
// diagonal half of the widest gap (~27px) so the grid centre still reaches a button.
|
||||
@@ -41,6 +43,7 @@ namespace games::jb {
|
||||
static std::atomic_bool TOUCH_ENABLE = false;
|
||||
static bool TOUCH_ATTACHED = false;
|
||||
static bool IS_PORTRAIT = true;
|
||||
static bool IS_T44 = false;
|
||||
static std::atomic_uint16_t TOUCH_STATE = 0;
|
||||
|
||||
// fixed-size contact view used by the debug overlay
|
||||
@@ -85,35 +88,45 @@ namespace games::jb {
|
||||
// --- touch geometry ------------------------------------------------------
|
||||
// gaps between the four buttons along one axis (the middle gap is 1px wider)
|
||||
static const int JB_BUTTON_GAPS[3] = { 37, JB_MAX_BUTTON_GAP, 37 };
|
||||
static const int JB_T44_BUTTON_GAPS[3] = {
|
||||
JB_T44_BUTTON_GAP,
|
||||
JB_T44_BUTTON_GAP,
|
||||
JB_T44_BUTTON_GAP,
|
||||
};
|
||||
|
||||
struct AxisGeometry {
|
||||
int size;
|
||||
int button[4]; // left/top edge of each button
|
||||
};
|
||||
|
||||
// left/top edges of the four buttons along one axis, starting at `first`
|
||||
static AxisGeometry axis_geometry(int first) {
|
||||
static AxisGeometry axis_geometry(int first, int size, const int gaps[3]) {
|
||||
AxisGeometry g {};
|
||||
g.size = size;
|
||||
g.button[0] = first;
|
||||
for (int i = 1; i < 4; i++) {
|
||||
g.button[i] = g.button[i - 1] + JB_BUTTON_SIZE + JB_BUTTON_GAPS[i - 1];
|
||||
g.button[i] = g.button[i - 1] + size + gaps[i - 1];
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
// button edges for the current orientation
|
||||
static void touch_geometry(AxisGeometry &gx, AxisGeometry &gy) {
|
||||
if (IS_PORTRAIT) {
|
||||
gx = axis_geometry(8);
|
||||
gy = axis_geometry(602);
|
||||
if (IS_T44) {
|
||||
gx = axis_geometry(37, JB_T44_BUTTON_SIZE, JB_T44_BUTTON_GAPS);
|
||||
gy = axis_geometry(864, JB_T44_BUTTON_SIZE, JB_T44_BUTTON_GAPS);
|
||||
} else if (IS_PORTRAIT) {
|
||||
gx = axis_geometry(8, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||
gy = axis_geometry(602, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||
} else {
|
||||
gx = axis_geometry(6);
|
||||
gy = axis_geometry(8);
|
||||
gx = axis_geometry(6, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||
gy = axis_geometry(8, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||
}
|
||||
}
|
||||
|
||||
// distance from `p` to a button along one axis (0 when inside)
|
||||
static int axis_distance(int p, int button) {
|
||||
int end = button + JB_BUTTON_SIZE - 1;
|
||||
static int axis_distance(int p, int button, int size) {
|
||||
int end = button + size - 1;
|
||||
if (p < button) {
|
||||
return button - p;
|
||||
}
|
||||
@@ -131,8 +144,8 @@ namespace games::jb {
|
||||
int best_dist = 0;
|
||||
for (int r = 0; r < 4; r++) {
|
||||
for (int c = 0; c < 4; c++) {
|
||||
int dx = axis_distance(px, gx.button[c]);
|
||||
int dy = axis_distance(py, gy.button[r]);
|
||||
int dx = axis_distance(px, gx.button[c], gx.size);
|
||||
int dy = axis_distance(py, gy.button[r], gy.size);
|
||||
int dist = dx * dx + dy * dy;
|
||||
if (dist <= radius * radius && (best_index < 0 || dist < best_dist)) {
|
||||
best_dist = dist;
|
||||
@@ -145,7 +158,8 @@ namespace games::jb {
|
||||
|
||||
// detection reach for the current algorithm (0 = register only inside a button)
|
||||
static int touch_radius() {
|
||||
return TOUCH_ALGORITHM == AcAccurate ? 0 : JB_TOUCH_RADIUS;
|
||||
return TOUCH_ALGORITHM == AcAccurate || (IS_T44 && TOUCH_ALGORITHM == Legacy) ?
|
||||
0 : JB_TOUCH_RADIUS;
|
||||
}
|
||||
|
||||
// mark the buttons a touch at (px, py) hits: only the nearest within `radius`, or
|
||||
@@ -162,8 +176,8 @@ namespace games::jb {
|
||||
}
|
||||
for (int r = 0; r < 4; r++) {
|
||||
for (int c = 0; c < 4; c++) {
|
||||
int dx = axis_distance(px, gx.button[c]);
|
||||
int dy = axis_distance(py, gy.button[r]);
|
||||
int dx = axis_distance(px, gx.button[c], gx.size);
|
||||
int dy = axis_distance(py, gy.button[r], gy.size);
|
||||
if (dx * dx + dy * dy <= radius * radius) {
|
||||
state |= uint16_t(1) << (r * 4 + c);
|
||||
}
|
||||
@@ -184,11 +198,15 @@ namespace games::jb {
|
||||
// one-time touch window attach
|
||||
if (!TOUCH_ATTACHED) {
|
||||
|
||||
IS_T44 = avs::game::is_model("T44");
|
||||
IS_PORTRAIT = IS_T44 || avs::game::is_model("L44");
|
||||
|
||||
// find the game window: prefer the foreground window, else search by
|
||||
// title (the model name prefixes the window title in every version)
|
||||
// title (T44 uses a fixed title instead of the model prefix)
|
||||
const char *window_title = IS_T44 ? "jubeat 10 main" : avs::game::MODEL;
|
||||
HWND wnd = GetForegroundWindow();
|
||||
if (!string_begins_with(GetActiveWindowTitle(), avs::game::MODEL)) {
|
||||
wnd = FindWindowBeginsWith(avs::game::MODEL);
|
||||
if (!string_begins_with(GetActiveWindowTitle(), window_title)) {
|
||||
wnd = FindWindowBeginsWith(window_title);
|
||||
}
|
||||
if (!wnd) {
|
||||
log_warning("jubeat", "could not find window handle for touch");
|
||||
@@ -197,10 +215,6 @@ namespace games::jb {
|
||||
return;
|
||||
}
|
||||
|
||||
// only the L44 model runs in portrait; set this before starting the
|
||||
// touch-window thread so the renderer only observes the final value
|
||||
IS_PORTRAIT = avs::game::is_model("L44");
|
||||
|
||||
log_info("jubeat", "using window handle for touch: {}", fmt::ptr(wnd));
|
||||
|
||||
// let the rawinput stack correct the aspect ratio
|
||||
@@ -232,7 +246,7 @@ namespace games::jb {
|
||||
return !touch_matured(tp, now_ms, threshold_ms);
|
||||
});
|
||||
|
||||
if (TOUCH_ALGORITHM == Legacy) {
|
||||
if (TOUCH_ALGORITHM == Legacy && !IS_T44) {
|
||||
|
||||
// legacy: evenly divide the play area into a 4x4 grid
|
||||
auto offset = IS_PORTRAIT ? 580 : 0;
|
||||
@@ -311,7 +325,7 @@ namespace games::jb {
|
||||
for (int c = 0; c < 4; c++) {
|
||||
cells[r * 4 + c] = {
|
||||
gx.button[c], gy.button[r],
|
||||
gx.button[c] + JB_BUTTON_SIZE, gy.button[r] + JB_BUTTON_SIZE
|
||||
gx.button[c] + gx.size, gy.button[r] + gy.size
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -352,8 +366,8 @@ namespace games::jb {
|
||||
const AxisGeometry &gx, const AxisGeometry &gy, int arc_radius) {
|
||||
int c = index % 4;
|
||||
int r = index / 4;
|
||||
double mid = std::atan2((gy.button[r] + JB_BUTTON_SIZE / 2) - py,
|
||||
(gx.button[c] + JB_BUTTON_SIZE / 2) - px);
|
||||
double mid = std::atan2((gy.button[r] + gy.size / 2) - py,
|
||||
(gx.button[c] + gx.size / 2) - px);
|
||||
const double quarter = 3.14159265358979323846 / 2.0;
|
||||
const int segments = 16;
|
||||
POINT arc[segments + 1];
|
||||
@@ -425,7 +439,7 @@ namespace games::jb {
|
||||
}
|
||||
|
||||
// legacy divides the field evenly; the other algorithms use button squares
|
||||
bool legacy = (TOUCH_ALGORITHM == Legacy);
|
||||
bool legacy = TOUCH_ALGORITHM == Legacy && !IS_T44;
|
||||
AxisGeometry gx {}, gy {};
|
||||
if (!legacy) {
|
||||
touch_geometry(gx, gy);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "poke.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
@@ -11,7 +12,6 @@
|
||||
#include "touch/native/inject.h"
|
||||
#include "touch/native/nativetouchhook.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
namespace games::nost::poke {
|
||||
|
||||
@@ -68,8 +68,6 @@ namespace games::nost::poke {
|
||||
// create new thread
|
||||
THREAD_RUNNING = true;
|
||||
THREAD = new std::thread([] {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
const int swipe_anim_total_frames = 6;
|
||||
const int swipe_anim_y = 300;
|
||||
|
||||
@@ -194,7 +192,7 @@ namespace games::nost::poke {
|
||||
}
|
||||
|
||||
// slow down
|
||||
timer.sleep(30);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
}
|
||||
|
||||
if (contact_active) {
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "hooks/audio/util.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
NullDiscardBackend::~NullDiscardBackend() {
|
||||
this->running = false;
|
||||
@@ -125,8 +125,6 @@ HRESULT NullDiscardBackend::on_release_buffer(uint32_t, DWORD) {
|
||||
void NullDiscardBackend::pace_loop() {
|
||||
using namespace std::chrono;
|
||||
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// audio is discarded, so timing precision and drift do not matter; just wake the
|
||||
// game once per buffer period to keep its render thread from blocking on the event.
|
||||
const auto period = duration_cast<steady_clock::duration>(
|
||||
@@ -136,6 +134,6 @@ void NullDiscardBackend::pace_loop() {
|
||||
if (this->relay_handle) {
|
||||
SetEvent(this->relay_handle);
|
||||
}
|
||||
timer.sleep(period);
|
||||
std::this_thread::sleep_for(period);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,10 @@ static Direct3DCreate9On12Ex_t Direct3DCreate9On12Ex_orig = nullptr;
|
||||
static bool ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE = false;
|
||||
static IDirect3DSwapChain9 *SUB_SWAP_CHAIN = nullptr;
|
||||
|
||||
static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRESENT_PARAMETERS *present_params);
|
||||
static void graphics_d3d9_ldj_init_sub_screen(
|
||||
IDirect3DDevice9Ex *device,
|
||||
D3DPRESENT_PARAMETERS *present_params,
|
||||
UINT gfdm_small_swapchain);
|
||||
|
||||
static std::string behavior2s(DWORD behavior_flags) {
|
||||
FLAGS_START(behavior_flags);
|
||||
@@ -409,7 +412,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::QueryInterface(
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_WrappedIDirect3D9 ||
|
||||
if ((riid == IID_IUnknown && gfdm_two_head_exclusive()) ||
|
||||
riid == IID_WrappedIDirect3D9 ||
|
||||
riid == IID_IDirect3D9 ||
|
||||
riid == IID_IDirect3D9Ex)
|
||||
{
|
||||
@@ -467,6 +471,18 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::RegisterSoftwareDevice(void *pIniti
|
||||
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterCount() {
|
||||
UINT result = pReal->GetAdapterCount();
|
||||
|
||||
if (gfdm_two_head_exclusive()) {
|
||||
if (result == 2) {
|
||||
FAKE_SUBSCREEN_ADAPTER = true;
|
||||
return GFDM_LOGICAL_HEAD_COUNT;
|
||||
}
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head mode requires two native adapters; found {}",
|
||||
result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!FAKE_SUBSCREEN_ADAPTER) {
|
||||
if (games::popn::is_pikapika_model() && result == 1) {
|
||||
FAKE_SUBSCREEN_ADAPTER = true;
|
||||
@@ -493,7 +509,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
|
||||
D3DADAPTER_IDENTIFIER9 *pIdentifier)
|
||||
{
|
||||
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0 && pIdentifier) {
|
||||
if (is_fake_subscreen_adapter(Adapter) && pIdentifier) {
|
||||
*pIdentifier = {};
|
||||
const std::string adapter_name = fmt::format("\\\\.\\DISPLAY_SPICE_FAKE_{}", Adapter);
|
||||
strcpy(pIdentifier->DeviceName, adapter_name.c_str());
|
||||
@@ -509,7 +525,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
|
||||
}
|
||||
|
||||
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) {
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0) {
|
||||
if (is_fake_subscreen_adapter(Adapter)) {
|
||||
log_misc("graphics::d3d9", "GetAdapterModeCount called for fake subscreen adapter {}", Adapter);
|
||||
return 1;
|
||||
}
|
||||
@@ -523,13 +539,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
UINT Mode,
|
||||
D3DDISPLAYMODE *pMode)
|
||||
{
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0) {
|
||||
if (is_fake_subscreen_adapter(Adapter)) {
|
||||
log_misc("graphics::d3d9", "EnumAdapterModes called for fake subscreen adapter {}", Adapter);
|
||||
if (Mode == 0 && pMode) {
|
||||
pMode->Width = 1280;
|
||||
pMode->Height = 800;
|
||||
pMode->RefreshRate = 60;
|
||||
pMode->Format = D3DFMT_X8R8G8B8;
|
||||
get_fake_subscreen_display_mode(Adapter, pMode);
|
||||
return S_OK;
|
||||
} else {
|
||||
return D3DERR_INVALIDCALL;
|
||||
@@ -611,6 +624,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterDisplayMode(UINT Adapter, D3DDISPLAYMODE *pMode) {
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
if (pMode == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
get_fake_subscreen_display_mode(Adapter, pMode);
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->GetAdapterDisplayMode(Adapter, pMode));
|
||||
}
|
||||
|
||||
@@ -621,6 +642,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceType(
|
||||
D3DFORMAT BackBufferFormat,
|
||||
BOOL bWindowed)
|
||||
{
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(iAdapter)) {
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->CheckDeviceType(iAdapter, DevType, DisplayFormat, BackBufferFormat, bWindowed));
|
||||
}
|
||||
|
||||
@@ -632,7 +657,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceFormat(
|
||||
D3DRESOURCETYPE RType,
|
||||
D3DFORMAT CheckFormat)
|
||||
{
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0) {
|
||||
if (is_fake_subscreen_adapter(Adapter)) {
|
||||
log_misc("graphics::d3d9", "CheckDeviceFormat called for fake subscreen adapter {}", Adapter);
|
||||
return D3D_OK;
|
||||
}
|
||||
@@ -648,6 +673,13 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceMultiSampleType(
|
||||
D3DMULTISAMPLE_TYPE MultiSampleType,
|
||||
DWORD *pQualityLevels)
|
||||
{
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
if (pQualityLevels != nullptr) {
|
||||
*pQualityLevels = 1;
|
||||
}
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->CheckDeviceMultiSampleType(
|
||||
Adapter,
|
||||
DeviceType,
|
||||
@@ -664,6 +696,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDepthStencilMatch(
|
||||
D3DFORMAT RenderTargetFormat,
|
||||
D3DFORMAT DepthStencilFormat)
|
||||
{
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->CheckDepthStencilMatch(
|
||||
Adapter,
|
||||
DeviceType,
|
||||
@@ -678,6 +714,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceFormatConversion(
|
||||
D3DFORMAT SourceFormat,
|
||||
D3DFORMAT TargetFormat)
|
||||
{
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->CheckDeviceFormatConversion(Adapter, DeviceType, SourceFormat, TargetFormat));
|
||||
}
|
||||
|
||||
@@ -689,6 +729,20 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (gfdm_two_head_exclusive() && Adapter < GFDM_LOGICAL_HEAD_COUNT) {
|
||||
HRESULT ret = this->pReal->GetDeviceCaps(
|
||||
Adapter >= 2 ? 0 : Adapter,
|
||||
DeviceType,
|
||||
pCaps);
|
||||
if (SUCCEEDED(ret)) {
|
||||
pCaps->NumberOfAdaptersInGroup =
|
||||
Adapter == 0 ? GFDM_LOGICAL_HEAD_COUNT : 0;
|
||||
pCaps->MasterAdapterOrdinal = 0;
|
||||
pCaps->AdapterOrdinalInGroup = Adapter;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// SDVX uses `NumberOfAdaptersInGroup` to allocate a vector and the Microsoft documentation states:
|
||||
// "The value will be 0 for a subordinate adapter of a multihead card. Each card can have at most one
|
||||
// master, but may have many subordinates." Therefore, this must point to the master adapter.
|
||||
@@ -741,6 +795,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
|
||||
}
|
||||
|
||||
HMONITOR STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterMonitor(UINT Adapter) {
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
return pReal->GetAdapterMonitor(0);
|
||||
}
|
||||
|
||||
return pReal->GetAdapterMonitor(Adapter);
|
||||
}
|
||||
|
||||
@@ -861,7 +919,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
"either use -graphics-force-refresh option or change the desktop resolution beforehand.");
|
||||
}
|
||||
|
||||
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
if (!GRAPHICS_WINDOWED && num_adapters >= 2
|
||||
&& GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz,
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value());
|
||||
@@ -902,7 +961,9 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
} else if (!D3D9_DEVICE_HOOK_DISABLE) {
|
||||
graphics_hook_window(hFocusWindow, pPresentationParameters);
|
||||
|
||||
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
|
||||
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(
|
||||
hFocusWindow,
|
||||
*ppReturnedDeviceInterface);
|
||||
}
|
||||
|
||||
// return result
|
||||
@@ -916,6 +977,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCountEx(UINT Adapter, const D3DDISPLAYMODEFILTER *pFilter) {
|
||||
assert(is_d3d9ex);
|
||||
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return static_cast<IDirect3D9Ex *>(pReal)->GetAdapterModeCountEx(Adapter, pFilter);
|
||||
}
|
||||
|
||||
@@ -926,6 +991,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModesEx(
|
||||
D3DDISPLAYMODEEX *pMode)
|
||||
{
|
||||
assert(is_d3d9ex);
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
if (Mode != 0 || pMode == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
get_fake_subscreen_display_mode_ex(Adapter, pMode);
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(static_cast<IDirect3D9Ex *>(pReal)->EnumAdapterModesEx(Adapter, pFilter, Mode, pMode));
|
||||
}
|
||||
|
||||
@@ -935,6 +1008,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterDisplayModeEx(
|
||||
D3DDISPLAYROTATION *pRotation)
|
||||
{
|
||||
assert(is_d3d9ex);
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
if (pMode == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
get_fake_subscreen_display_mode_ex(Adapter, pMode);
|
||||
if (pRotation != nullptr) {
|
||||
*pRotation = D3DDISPLAYROTATION_IDENTITY;
|
||||
}
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(static_cast<IDirect3D9Ex *>(pReal)->GetAdapterDisplayModeEx(Adapter, pMode, pRotation));
|
||||
}
|
||||
|
||||
@@ -963,6 +1047,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
|
||||
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;
|
||||
size_t num_adapters = 1;
|
||||
@@ -989,6 +1081,9 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
if (SUCCEEDED(this->pReal->GetDeviceCaps(Adapter, DeviceType, &device_caps))) {
|
||||
num_adapters = device_caps.NumberOfAdaptersInGroup;
|
||||
}
|
||||
if (gfdm_two_head_exclusive()) {
|
||||
num_adapters = GFDM_LOGICAL_HEAD_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_adapters; i++) {
|
||||
@@ -1105,7 +1200,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
}
|
||||
}
|
||||
|
||||
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
if (!GRAPHICS_WINDOWED && !gfdm_two_head_exclusive()
|
||||
&& num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz,
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value());
|
||||
@@ -1131,16 +1227,93 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
pPresentationParameters->BackBufferCount = GRAPHICS_FORCE_VSYNC_BUFFER.value();
|
||||
}
|
||||
|
||||
GfdmTwoHeadDeviceState gfdm_parameters(
|
||||
pPresentationParameters,
|
||||
pFullscreenDisplayMode);
|
||||
if (gfdm_two_head_exclusive()) {
|
||||
if (!(BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"CreateDeviceEx did not request an adapter-group device");
|
||||
return D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
HRESULT select = graphics_d3d9_gfdm_select_two_head_group_parameters(
|
||||
pPresentationParameters,
|
||||
pFullscreenDisplayMode,
|
||||
gfdm_parameters.native_presentation_parameters.data(),
|
||||
gfdm_parameters.native_fullscreen_display_modes.data(),
|
||||
&gfdm_parameters.logical_small_swapchain,
|
||||
"CreateDeviceEx");
|
||||
if (FAILED(select)) {
|
||||
return select;
|
||||
}
|
||||
if (GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
gfdm_parameters.native_presentation_parameters[1]
|
||||
.FullScreen_RefreshRateInHz =
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
if (pFullscreenDisplayMode != nullptr) {
|
||||
gfdm_parameters.native_fullscreen_display_modes[1].RefreshRate =
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
}
|
||||
}
|
||||
HRESULT remap = graphics_d3d9_gfdm_remap_two_head_group_parameters(
|
||||
gfdm_parameters.native_presentation_parameters.data(),
|
||||
pFullscreenDisplayMode != nullptr
|
||||
? gfdm_parameters.native_fullscreen_display_modes.data()
|
||||
: nullptr,
|
||||
"CreateDeviceEx");
|
||||
if (FAILED(remap)) {
|
||||
return remap;
|
||||
}
|
||||
if (pFullscreenDisplayMode != nullptr) {
|
||||
graphics_d3d9_gfdm_align_two_head_refresh_to_desktop(
|
||||
static_cast<IDirect3D9Ex *>(this->pReal),
|
||||
Adapter,
|
||||
gfdm_parameters.native_presentation_parameters.data(),
|
||||
gfdm_parameters.native_fullscreen_display_modes.data(),
|
||||
"CreateDeviceEx");
|
||||
}
|
||||
if (!graphics_gitadora_prepare_two_head_device_window(
|
||||
gfdm_parameters.native_presentation_parameters[1].hDeviceWindow,
|
||||
this->pReal->GetAdapterMonitor(Adapter + 1),
|
||||
gfdm_parameters.native_presentation_parameters[1].BackBufferWidth,
|
||||
gfdm_parameters.native_presentation_parameters[1].BackBufferHeight))
|
||||
{
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
HRESULT validation = validate_gfdm_two_head_exclusive(
|
||||
this->pReal,
|
||||
Adapter,
|
||||
DeviceType,
|
||||
BehaviorFlags,
|
||||
gfdm_parameters.native_presentation_parameters.data());
|
||||
if (FAILED(validation)) {
|
||||
return validation;
|
||||
}
|
||||
gfdm_parameters.use_native_parameters();
|
||||
}
|
||||
|
||||
// call original
|
||||
HRESULT result = static_cast<IDirect3D9Ex *>(this->pReal)->CreateDeviceEx(
|
||||
Adapter,
|
||||
DeviceType,
|
||||
hFocusWindow,
|
||||
BehaviorFlags,
|
||||
pPresentationParameters,
|
||||
pFullscreenDisplayMode,
|
||||
gfdm_parameters.presentation_parameters,
|
||||
gfdm_parameters.fullscreen_display_modes,
|
||||
ppReturnedDeviceInterface);
|
||||
|
||||
if (SUCCEEDED(result) && gfdm_two_head_exclusive()) {
|
||||
pPresentationParameters[0] = gfdm_parameters.presentation_parameters[0];
|
||||
pPresentationParameters[gfdm_parameters.logical_small_swapchain] =
|
||||
gfdm_parameters.presentation_parameters[1];
|
||||
if (pFullscreenDisplayMode != nullptr) {
|
||||
pFullscreenDisplayMode[0] = gfdm_parameters.fullscreen_display_modes[0];
|
||||
pFullscreenDisplayMode[gfdm_parameters.logical_small_swapchain] =
|
||||
gfdm_parameters.fullscreen_display_modes[1];
|
||||
}
|
||||
}
|
||||
|
||||
// check for error
|
||||
if (result != D3D_OK) {
|
||||
|
||||
@@ -1151,17 +1324,28 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
} else if (!D3D9_DEVICE_HOOK_DISABLE) {
|
||||
graphics_hook_window(hFocusWindow, pPresentationParameters);
|
||||
|
||||
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
|
||||
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(
|
||||
hFocusWindow,
|
||||
*ppReturnedDeviceInterface,
|
||||
gfdm_parameters.logical_small_swapchain,
|
||||
gfdm_two_head_exclusive() ? static_cast<IDirect3D9 *>(this) : nullptr,
|
||||
gfdm_two_head_exclusive() ? pPresentationParameters : nullptr);
|
||||
|
||||
// initialize sub screen if the game requested a multi-head context
|
||||
if (avs::game::is_model({"LDJ", "KFC", "M39", "M32"}) &&
|
||||
(orig_behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
|
||||
|
||||
UINT i = 1;
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
i = 2;
|
||||
}
|
||||
graphics_d3d9_ldj_init_sub_screen(*ppReturnedDeviceInterface, &pPresentationParameters[i]);
|
||||
UINT i = games::gitadora::is_arena_model()
|
||||
? gfdm_parameters.logical_small_swapchain
|
||||
: 1;
|
||||
D3DPRESENT_PARAMETERS *subscreen_parameters =
|
||||
gfdm_two_head_exclusive()
|
||||
? &gfdm_parameters.presentation_parameters[1]
|
||||
: &pPresentationParameters[i];
|
||||
graphics_d3d9_ldj_init_sub_screen(
|
||||
*ppReturnedDeviceInterface,
|
||||
subscreen_parameters,
|
||||
i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1170,6 +1354,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterLUID(UINT Adapter, LUID *pLUID) {
|
||||
assert(is_d3d9ex);
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
if (pLUID == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
pLUID->LowPart = static_cast<DWORD>(-static_cast<LONG>(Adapter));
|
||||
pLUID->HighPart = -static_cast<LONG>(Adapter);
|
||||
return D3D_OK;
|
||||
}
|
||||
CHECK_RESULT(static_cast<IDirect3D9Ex *>(pReal)->GetAdapterLUID(Adapter, pLUID));
|
||||
}
|
||||
|
||||
@@ -1178,7 +1370,11 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterLUID(UINT Adapter, LUID *
|
||||
// The sub screen swap chain should be created if:
|
||||
// - Running windowed with `NumberOfAdaptersInGroup >= 2` (game expects implicit swap chain to exist)
|
||||
// - Running fullscreen with `NumberOfAdaptersInGroup < 2` (overridden `GetDeviceCaps` structure)
|
||||
static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRESENT_PARAMETERS *present_params) {
|
||||
static void graphics_d3d9_ldj_init_sub_screen(
|
||||
IDirect3DDevice9Ex *device,
|
||||
D3DPRESENT_PARAMETERS *present_params,
|
||||
UINT gfdm_small_swapchain)
|
||||
{
|
||||
D3DCAPS9 caps {};
|
||||
HRESULT hr = device->GetDeviceCaps(&caps);
|
||||
if (FAILED(hr)) {
|
||||
@@ -1212,10 +1408,9 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
|
||||
}
|
||||
} else {
|
||||
|
||||
int swapchain = 1;
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
swapchain = 2;
|
||||
}
|
||||
const int swapchain = games::gitadora::is_arena_model()
|
||||
? static_cast<int>(gfdm_small_swapchain)
|
||||
: 1;
|
||||
|
||||
hr = device->GetSwapChain(swapchain, &SUB_SWAP_CHAIN);
|
||||
if (FAILED(hr)) {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <d3d9.h>
|
||||
|
||||
#include "d3d9_gfdm.h"
|
||||
|
||||
// {EEE9CCF6-53D6-4326-9AE5-60921B3DB394}
|
||||
static const GUID IID_WrappedIDirect3D9 = {
|
||||
0xeee9ccf6, 0x53d6, 0x4326, { 0x9a, 0xe5, 0x60, 0x92, 0x1b, 0x3d, 0xb3, 0x94 }
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
constexpr bool CUSTOM_RESET = false;
|
||||
|
||||
constexpr D3DFORMAT D3DFMT_DF24 = static_cast<D3DFORMAT>(MAKEFOURCC('D', 'F', '2', '4'));
|
||||
|
||||
auto format_as(D3DPOOL f) {
|
||||
return fmt::underlying(f);
|
||||
}
|
||||
@@ -85,7 +84,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::QueryInterface(
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_WrappedIDirect3DDevice9 ||
|
||||
if ((riid == IID_IUnknown && is_gfdm_two_head_exclusive()) ||
|
||||
riid == IID_WrappedIDirect3DDevice9 ||
|
||||
riid == IID_IDirect3DDevice9 ||
|
||||
riid == IID_IDirect3DDevice9Ex)
|
||||
{
|
||||
@@ -202,6 +202,15 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetDirect3D(
|
||||
IDirect3D9 **ppD3D9)
|
||||
{
|
||||
WRAP_VERBOSE;
|
||||
if (is_gfdm_two_head_exclusive() && gfdm_parent_d3d != nullptr) {
|
||||
if (ppD3D9 == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
gfdm_parent_d3d->AddRef();
|
||||
*ppD3D9 = gfdm_parent_d3d;
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->GetDirect3D(ppD3D9));
|
||||
}
|
||||
|
||||
@@ -209,7 +218,13 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetDeviceCaps(
|
||||
D3DCAPS9 *pCaps)
|
||||
{
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal->GetDeviceCaps(pCaps));
|
||||
HRESULT result = pReal->GetDeviceCaps(pCaps);
|
||||
if (SUCCEEDED(result) && is_gfdm_two_head_exclusive() && pCaps != nullptr) {
|
||||
pCaps->NumberOfAdaptersInGroup = GFDM_LOGICAL_HEAD_COUNT;
|
||||
pCaps->MasterAdapterOrdinal = 0;
|
||||
pCaps->AdapterOrdinalInGroup = 0;
|
||||
}
|
||||
CHECK_RESULT(result);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetDisplayMode(
|
||||
@@ -217,6 +232,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetDisplayMode(
|
||||
D3DDISPLAYMODE *pMode)
|
||||
{
|
||||
WRAP_VERBOSE;
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_side_swapchain(iSwapChain))
|
||||
{
|
||||
return ensure_gfdm_hidden_side_swapchain(iSwapChain)->GetDisplayMode(pMode);
|
||||
}
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_small_swapchain(iSwapChain))
|
||||
{
|
||||
CHECK_RESULT(pReal->GetDisplayMode(GFDM_NATIVE_SMALL_SWAPCHAIN, pMode));
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->GetDisplayMode(iSwapChain, pMode));
|
||||
}
|
||||
|
||||
@@ -258,6 +284,51 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
|
||||
{
|
||||
WRAP_VERBOSE;
|
||||
|
||||
if (pPresentationParameters == nullptr || ppSwapChain == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (is_gfdm_two_head_exclusive()) {
|
||||
if (pPresentationParameters->BackBufferWidth == 800) {
|
||||
return GetSwapChain(gfdm_logical_small_swapchain, ppSwapChain);
|
||||
}
|
||||
if (pPresentationParameters->BackBufferWidth != 1080) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: rejecting unexpected additional swap chain {}x{}",
|
||||
pPresentationParameters->BackBufferWidth,
|
||||
pPresentationParameters->BackBufferHeight);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
size_t index = 3;
|
||||
for (UINT logical_swapchain = 1;
|
||||
logical_swapchain < GFDM_LOGICAL_HEAD_COUNT;
|
||||
logical_swapchain++)
|
||||
{
|
||||
if (!is_gfdm_logical_side_swapchain(logical_swapchain)) {
|
||||
continue;
|
||||
}
|
||||
const size_t candidate =
|
||||
gfdm_hidden_side_swapchain_slot(logical_swapchain);
|
||||
if (fake_sub_swapchain[candidate] == nullptr) {
|
||||
index = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index == 3) {
|
||||
return D3DERR_OUTOFVIDEOMEMORY;
|
||||
}
|
||||
fake_sub_swapchain[index] = new FakeIDirect3DSwapChain9(
|
||||
this,
|
||||
pPresentationParameters,
|
||||
is_d3d9ex,
|
||||
true);
|
||||
fake_sub_swapchain[index]->AddRef();
|
||||
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(fake_sub_swapchain[index]);
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
bool create_swap_chain = false;
|
||||
bool create_fake_swap_chain = false;
|
||||
@@ -271,6 +342,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
|
||||
// SMALL (subscreen)
|
||||
create_swap_chain = true;
|
||||
index = 0;
|
||||
|
||||
} else if (pPresentationParameters->BackBufferWidth == 1080) {
|
||||
// LEFT/RIGHT
|
||||
create_swap_chain = true;
|
||||
@@ -335,6 +407,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
|
||||
{
|
||||
WRAP_VERBOSE_FMT("GetSwapChain({})", iSwapChain);
|
||||
|
||||
if (ppSwapChain == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (iSwapChain == 0) {
|
||||
if (!main_swapchain) {
|
||||
HRESULT ret = pReal->GetSwapChain(iSwapChain, ppSwapChain);
|
||||
@@ -344,7 +420,12 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
|
||||
return ret;
|
||||
}
|
||||
|
||||
main_swapchain = new WrappedIDirect3DSwapChain9(this, *ppSwapChain);
|
||||
main_swapchain = new WrappedIDirect3DSwapChain9(
|
||||
this,
|
||||
*ppSwapChain,
|
||||
is_gfdm_two_head_exclusive()
|
||||
? WrappedIDirect3DSwapChain9::NativeGroupHead::Main
|
||||
: WrappedIDirect3DSwapChain9::NativeGroupHead::Unknown);
|
||||
}
|
||||
|
||||
main_swapchain->AddRef();
|
||||
@@ -354,6 +435,39 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
if (is_gfdm_two_head_exclusive()) {
|
||||
if (is_gfdm_logical_small_swapchain(iSwapChain)) {
|
||||
if (!sub_swapchain[0]) {
|
||||
IDirect3DSwapChain9 *real_swapchain = nullptr;
|
||||
HRESULT ret = pReal->GetSwapChain(
|
||||
GFDM_NATIVE_SMALL_SWAPCHAIN,
|
||||
&real_swapchain);
|
||||
if (FAILED(ret)) {
|
||||
return ret;
|
||||
}
|
||||
sub_swapchain[0] = new WrappedIDirect3DSwapChain9(
|
||||
this,
|
||||
real_swapchain,
|
||||
WrappedIDirect3DSwapChain9::NativeGroupHead::Small);
|
||||
sub_swapchain[0]->should_run_hooks = false;
|
||||
}
|
||||
|
||||
sub_swapchain[0]->AddRef();
|
||||
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(sub_swapchain[0]);
|
||||
graphics_screens_register(iSwapChain);
|
||||
return D3D_OK;
|
||||
}
|
||||
if (is_gfdm_logical_side_swapchain(iSwapChain)) {
|
||||
FakeIDirect3DSwapChain9 *swapchain =
|
||||
ensure_gfdm_hidden_side_swapchain(iSwapChain);
|
||||
swapchain->AddRef();
|
||||
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(swapchain);
|
||||
graphics_screens_register(iSwapChain);
|
||||
return D3D_OK;
|
||||
}
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
bool swap = false;
|
||||
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC", "M39"})) {
|
||||
swap = true;
|
||||
@@ -390,6 +504,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
|
||||
UINT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetNumberOfSwapChains() {
|
||||
WRAP_VERBOSE;
|
||||
|
||||
if (is_gfdm_two_head_exclusive()) {
|
||||
return GFDM_LOGICAL_HEAD_COUNT;
|
||||
}
|
||||
|
||||
UINT n = pReal->GetNumberOfSwapChains();
|
||||
|
||||
if (sub_swapchain[0] && avs::game::is_model({"LDJ", "KFC", "M39"})) {
|
||||
@@ -453,6 +571,24 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetBackBuffer(
|
||||
IDirect3DSurface9 **ppBackBuffer)
|
||||
{
|
||||
WRAP_VERBOSE;
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_side_swapchain(iSwapChain))
|
||||
{
|
||||
return ensure_gfdm_hidden_side_swapchain(iSwapChain)->GetBackBuffer(
|
||||
iBackBuffer,
|
||||
Type,
|
||||
ppBackBuffer);
|
||||
}
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_small_swapchain(iSwapChain))
|
||||
{
|
||||
CHECK_RESULT(pReal->GetBackBuffer(
|
||||
GFDM_NATIVE_SMALL_SWAPCHAIN,
|
||||
iBackBuffer,
|
||||
Type,
|
||||
ppBackBuffer));
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->GetBackBuffer(iSwapChain, iBackBuffer, Type, ppBackBuffer));
|
||||
}
|
||||
|
||||
@@ -461,6 +597,20 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetRasterStatus(
|
||||
D3DRASTER_STATUS *pRasterStatus)
|
||||
{
|
||||
WRAP_DEBUG;
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_side_swapchain(iSwapChain))
|
||||
{
|
||||
return ensure_gfdm_hidden_side_swapchain(iSwapChain)->GetRasterStatus(
|
||||
pRasterStatus);
|
||||
}
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_small_swapchain(iSwapChain))
|
||||
{
|
||||
CHECK_RESULT(pReal->GetRasterStatus(
|
||||
GFDM_NATIVE_SMALL_SWAPCHAIN,
|
||||
pRasterStatus));
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->GetRasterStatus(iSwapChain, pRasterStatus));
|
||||
}
|
||||
|
||||
@@ -477,6 +627,18 @@ void STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetGammaRamp(
|
||||
const D3DGAMMARAMP *pRamp)
|
||||
{
|
||||
WRAP_DEBUG;
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_side_swapchain(iSwapChain))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_small_swapchain(iSwapChain))
|
||||
{
|
||||
pReal->SetGammaRamp(GFDM_NATIVE_SMALL_SWAPCHAIN, Flags, pRamp);
|
||||
return;
|
||||
}
|
||||
|
||||
pReal->SetGammaRamp(iSwapChain, Flags, pRamp);
|
||||
}
|
||||
|
||||
@@ -485,6 +647,21 @@ void STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetGammaRamp(
|
||||
D3DGAMMARAMP *pRamp)
|
||||
{
|
||||
WRAP_DEBUG;
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_side_swapchain(iSwapChain))
|
||||
{
|
||||
if (pRamp != nullptr) {
|
||||
*pRamp = {};
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_small_swapchain(iSwapChain))
|
||||
{
|
||||
pReal->GetGammaRamp(GFDM_NATIVE_SMALL_SWAPCHAIN, pRamp);
|
||||
return;
|
||||
}
|
||||
|
||||
pReal->GetGammaRamp(iSwapChain, pRamp);
|
||||
}
|
||||
|
||||
@@ -685,6 +862,19 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetFrontBufferData(
|
||||
IDirect3DSurface9 *pDestSurface)
|
||||
{
|
||||
WRAP_DEBUG;
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_side_swapchain(iSwapChain))
|
||||
{
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_small_swapchain(iSwapChain))
|
||||
{
|
||||
CHECK_RESULT(pReal->GetFrontBufferData(
|
||||
GFDM_NATIVE_SMALL_SWAPCHAIN,
|
||||
pDestSurface));
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->GetFrontBufferData(iSwapChain, pDestSurface));
|
||||
}
|
||||
|
||||
@@ -1049,7 +1239,36 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetViewport(
|
||||
D3DVIEWPORT9 *pViewport)
|
||||
{
|
||||
WRAP_DEBUG;
|
||||
CHECK_RESULT(pReal->GetViewport(pViewport));
|
||||
const auto result = pReal->GetViewport(pViewport);
|
||||
|
||||
// SDVX landscape mode
|
||||
// without this, the game calculates the camera angle incorrectly and
|
||||
// ends up with zoomed out view of the lanes
|
||||
const auto landscape_width = GRAPHICS_FS_CUSTOM_RESOLUTION.has_value() ?
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first : GRAPHICS_FS_ORIGINAL_HEIGHT;
|
||||
const auto landscape_height = GRAPHICS_FS_CUSTOM_RESOLUTION.has_value() ?
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second : GRAPHICS_FS_ORIGINAL_WIDTH;
|
||||
if (SUCCEEDED(result) &&
|
||||
GRAPHICS_FS_ORIENTATION_SWAP &&
|
||||
avs::game::is_model("KFC") &&
|
||||
pViewport != nullptr &&
|
||||
pViewport->Width == landscape_width &&
|
||||
pViewport->Height == landscape_height) {
|
||||
|
||||
static std::once_flag log_once;
|
||||
std::call_once(log_once, [pViewport]() {
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"SDVX landscape viewport fix: {}x{} => {}x{}",
|
||||
pViewport->Width,
|
||||
pViewport->Height,
|
||||
pViewport->Height,
|
||||
pViewport->Width);
|
||||
});
|
||||
std::swap(pViewport->Width, pViewport->Height);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetMaterial(
|
||||
@@ -1779,6 +1998,18 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::WaitForVBlank(
|
||||
WRAP_DEBUG;
|
||||
|
||||
assert(is_d3d9ex);
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_side_swapchain(iSwapChain))
|
||||
{
|
||||
return D3D_OK;
|
||||
}
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_small_swapchain(iSwapChain))
|
||||
{
|
||||
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->WaitForVBlank(
|
||||
GFDM_NATIVE_SMALL_SWAPCHAIN));
|
||||
}
|
||||
|
||||
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->WaitForVBlank(iSwapChain));
|
||||
}
|
||||
|
||||
@@ -1882,6 +2113,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ResetEx(
|
||||
|
||||
log_misc("graphics::d3d9", "WrappedIDirect3DDevice9::ResetEx");
|
||||
|
||||
if (is_gfdm_two_head_exclusive()) {
|
||||
gfdm_disarm_present_mode_recovery();
|
||||
if (pPresentationParameters == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
release_gfdm_hidden_side_swapchains();
|
||||
}
|
||||
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
if (pPresentationParameters) {
|
||||
pPresentationParameters->Windowed = true;
|
||||
@@ -1898,13 +2137,115 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ResetEx(
|
||||
}
|
||||
}
|
||||
|
||||
GfdmTwoHeadDeviceState gfdm_parameters(
|
||||
pPresentationParameters,
|
||||
pFullscreenDisplayMode,
|
||||
gfdm_logical_small_swapchain);
|
||||
if (is_gfdm_two_head_exclusive()) {
|
||||
HRESULT result = graphics_d3d9_gfdm_select_two_head_group_parameters(
|
||||
pPresentationParameters,
|
||||
pFullscreenDisplayMode,
|
||||
gfdm_parameters.native_presentation_parameters.data(),
|
||||
pFullscreenDisplayMode != nullptr
|
||||
? gfdm_parameters.native_fullscreen_display_modes.data()
|
||||
: nullptr,
|
||||
&gfdm_parameters.logical_small_swapchain,
|
||||
"ResetEx");
|
||||
if (FAILED(result)) {
|
||||
return result;
|
||||
}
|
||||
set_gfdm_logical_group_parameters(pPresentationParameters);
|
||||
gfdm_parameters.use_native_parameters();
|
||||
if (gfdm_parameters.native_presentation_parameters[0].Windowed
|
||||
|| gfdm_parameters.native_presentation_parameters[1].Windowed)
|
||||
{
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
if (GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
gfdm_parameters.native_presentation_parameters[1]
|
||||
.FullScreen_RefreshRateInHz =
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
if (gfdm_parameters.fullscreen_display_modes != nullptr) {
|
||||
gfdm_parameters.fullscreen_display_modes[1].RefreshRate =
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
}
|
||||
}
|
||||
result = graphics_d3d9_gfdm_remap_two_head_group_parameters(
|
||||
gfdm_parameters.native_presentation_parameters.data(),
|
||||
gfdm_parameters.fullscreen_display_modes,
|
||||
"ResetEx");
|
||||
if (FAILED(result)) {
|
||||
return result;
|
||||
}
|
||||
if (!graphics_gitadora_prepare_two_head_device_window(
|
||||
gfdm_parameters.native_presentation_parameters[1].hDeviceWindow,
|
||||
nullptr,
|
||||
gfdm_parameters.native_presentation_parameters[1].BackBufferWidth,
|
||||
gfdm_parameters.native_presentation_parameters[1].BackBufferHeight))
|
||||
{
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
if (gfdm_parameters.fullscreen_display_modes != nullptr) {
|
||||
IDirect3D9 *raw_d3d = nullptr;
|
||||
IDirect3D9Ex *raw_d3d_ex = nullptr;
|
||||
D3DDEVICE_CREATION_PARAMETERS creation {};
|
||||
HRESULT raw_result = pReal->GetDirect3D(&raw_d3d);
|
||||
if (SUCCEEDED(raw_result) && raw_d3d != nullptr) {
|
||||
raw_result = raw_d3d->QueryInterface(IID_PPV_ARGS(&raw_d3d_ex));
|
||||
}
|
||||
if (SUCCEEDED(raw_result)) {
|
||||
raw_result = pReal->GetCreationParameters(&creation);
|
||||
}
|
||||
if (SUCCEEDED(raw_result) && raw_d3d_ex != nullptr) {
|
||||
graphics_d3d9_gfdm_align_two_head_refresh_to_desktop(
|
||||
raw_d3d_ex,
|
||||
creation.AdapterOrdinal,
|
||||
gfdm_parameters.native_presentation_parameters.data(),
|
||||
gfdm_parameters.fullscreen_display_modes,
|
||||
"ResetEx");
|
||||
}
|
||||
if (raw_d3d_ex != nullptr) {
|
||||
raw_d3d_ex->Release();
|
||||
}
|
||||
if (raw_d3d != nullptr) {
|
||||
raw_d3d->Release();
|
||||
}
|
||||
gfdm_parameters.recovery_parameters =
|
||||
gfdm_parameters.native_presentation_parameters;
|
||||
gfdm_parameters.recovery_modes =
|
||||
gfdm_parameters.native_fullscreen_display_modes;
|
||||
gfdm_parameters.recovery_candidate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// reset overlay
|
||||
if (overlay::OVERLAY && overlay::OVERLAY->uses_device(pReal)) {
|
||||
overlay::OVERLAY->reset_invalidate();
|
||||
}
|
||||
|
||||
HRESULT res = static_cast<IDirect3DDevice9Ex *>(pReal)->ResetEx(
|
||||
pPresentationParameters, pFullscreenDisplayMode);
|
||||
gfdm_parameters.presentation_parameters,
|
||||
gfdm_parameters.fullscreen_display_modes);
|
||||
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& SUCCEEDED(res)
|
||||
&& gfdm_parameters.recovery_candidate)
|
||||
{
|
||||
gfdm_arm_present_mode_recovery(
|
||||
gfdm_parameters.recovery_parameters.data(),
|
||||
gfdm_parameters.recovery_modes.data());
|
||||
}
|
||||
if (is_gfdm_two_head_exclusive() && SUCCEEDED(res)) {
|
||||
gfdm_logical_small_swapchain = gfdm_parameters.logical_small_swapchain;
|
||||
pPresentationParameters[0] = gfdm_parameters.presentation_parameters[0];
|
||||
pPresentationParameters[gfdm_parameters.logical_small_swapchain] =
|
||||
gfdm_parameters.presentation_parameters[1];
|
||||
if (pFullscreenDisplayMode != nullptr) {
|
||||
pFullscreenDisplayMode[0] = gfdm_parameters.fullscreen_display_modes[0];
|
||||
pFullscreenDisplayMode[gfdm_parameters.logical_small_swapchain] =
|
||||
gfdm_parameters.fullscreen_display_modes[1];
|
||||
}
|
||||
}
|
||||
|
||||
// recreate overlay
|
||||
if (overlay::OVERLAY && overlay::OVERLAY->uses_device(pReal) && SUCCEEDED(res)) {
|
||||
@@ -1922,6 +2263,22 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetDisplayModeEx(
|
||||
WRAP_DEBUG;
|
||||
|
||||
assert(is_d3d9ex);
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_side_swapchain(iSwapChain))
|
||||
{
|
||||
return ensure_gfdm_hidden_side_swapchain(iSwapChain)->GetDisplayModeEx(
|
||||
pMode,
|
||||
pRotation);
|
||||
}
|
||||
if (is_gfdm_two_head_exclusive()
|
||||
&& is_gfdm_logical_small_swapchain(iSwapChain))
|
||||
{
|
||||
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->GetDisplayModeEx(
|
||||
GFDM_NATIVE_SMALL_SWAPCHAIN,
|
||||
pMode,
|
||||
pRotation));
|
||||
}
|
||||
|
||||
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->GetDisplayModeEx(
|
||||
iSwapChain, pMode, pRotation));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#include <initguid.h>
|
||||
#include <d3d9.h>
|
||||
@@ -40,23 +42,54 @@ static const GUID IID_WrappedIDirect3DDevice9 = {
|
||||
void SurfaceHook(IDirect3DDevice9 *pReal);
|
||||
|
||||
struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex {
|
||||
explicit WrappedIDirect3DDevice9(HWND hFocusWindow, IDirect3DDevice9 *orig)
|
||||
: hFocusWindow(hFocusWindow), pReal(orig), is_d3d9ex(false) {
|
||||
explicit WrappedIDirect3DDevice9(
|
||||
HWND hFocusWindow,
|
||||
IDirect3DDevice9 *orig,
|
||||
UINT gfdm_logical_small_swapchain = 2,
|
||||
IDirect3D9 *gfdm_parent_d3d = nullptr,
|
||||
const D3DPRESENT_PARAMETERS *gfdm_logical_group_parameters = nullptr)
|
||||
: hFocusWindow(hFocusWindow),
|
||||
pReal(orig),
|
||||
is_d3d9ex(false),
|
||||
gfdm_logical_small_swapchain(gfdm_logical_small_swapchain),
|
||||
gfdm_parent_d3d(gfdm_parent_d3d) {
|
||||
if (this->gfdm_parent_d3d != nullptr) {
|
||||
this->gfdm_parent_d3d->AddRef();
|
||||
}
|
||||
IDirect3DDevice9Ex *device = nullptr;
|
||||
|
||||
// attempt to upgrade handle
|
||||
if (SUCCEEDED(this->QueryInterface(IID_PPV_ARGS(&device))) && device != nullptr) {
|
||||
device->Release();
|
||||
}
|
||||
set_gfdm_logical_group_parameters(gfdm_logical_group_parameters);
|
||||
}
|
||||
|
||||
explicit WrappedIDirect3DDevice9(HWND hFocusWindow, IDirect3DDevice9Ex *orig)
|
||||
: hFocusWindow(hFocusWindow), pReal(orig), is_d3d9ex(true) {}
|
||||
explicit WrappedIDirect3DDevice9(
|
||||
HWND hFocusWindow,
|
||||
IDirect3DDevice9Ex *orig,
|
||||
UINT gfdm_logical_small_swapchain = 2,
|
||||
IDirect3D9 *gfdm_parent_d3d = nullptr,
|
||||
const D3DPRESENT_PARAMETERS *gfdm_logical_group_parameters = nullptr)
|
||||
: hFocusWindow(hFocusWindow),
|
||||
pReal(orig),
|
||||
is_d3d9ex(true),
|
||||
gfdm_logical_small_swapchain(gfdm_logical_small_swapchain),
|
||||
gfdm_parent_d3d(gfdm_parent_d3d) {
|
||||
if (this->gfdm_parent_d3d != nullptr) {
|
||||
this->gfdm_parent_d3d->AddRef();
|
||||
}
|
||||
set_gfdm_logical_group_parameters(gfdm_logical_group_parameters);
|
||||
}
|
||||
|
||||
WrappedIDirect3DDevice9(const WrappedIDirect3DDevice9 &) = delete;
|
||||
WrappedIDirect3DDevice9 &operator=(const WrappedIDirect3DDevice9 &) = delete;
|
||||
|
||||
virtual ~WrappedIDirect3DDevice9() = default;
|
||||
virtual ~WrappedIDirect3DDevice9() {
|
||||
if (gfdm_parent_d3d != nullptr) {
|
||||
gfdm_parent_d3d->Release();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma region IUnknown
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
@@ -201,6 +234,27 @@ struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex {
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDisplayModeEx(UINT iSwapChain, D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) override;
|
||||
#pragma endregion
|
||||
|
||||
bool is_gfdm_two_head_exclusive() const;
|
||||
bool is_gfdm_logical_small_swapchain(UINT swapchain) const;
|
||||
bool is_gfdm_logical_side_swapchain(UINT swapchain) const;
|
||||
size_t gfdm_hidden_side_swapchain_slot(UINT swapchain) const;
|
||||
void set_gfdm_logical_group_parameters(
|
||||
const D3DPRESENT_PARAMETERS *presentation_parameters);
|
||||
FakeIDirect3DSwapChain9 *ensure_gfdm_hidden_side_swapchain(UINT iSwapChain);
|
||||
void release_gfdm_hidden_side_swapchains();
|
||||
|
||||
enum class GfdmPresentModeRecoveryResult {
|
||||
NotAttempted,
|
||||
ReadyToRetry,
|
||||
Failed,
|
||||
};
|
||||
void gfdm_disarm_present_mode_recovery();
|
||||
void gfdm_arm_present_mode_recovery(
|
||||
const D3DPRESENT_PARAMETERS *native_presentation_parameters,
|
||||
const D3DDISPLAYMODEEX *native_fullscreen_display_modes);
|
||||
GfdmPresentModeRecoveryResult gfdm_recover_present_mode_change(
|
||||
HRESULT *failure_result);
|
||||
|
||||
HWND const hFocusWindow;
|
||||
IDirect3DDevice9 *pReal;
|
||||
bool is_d3d9ex = false;
|
||||
@@ -211,5 +265,17 @@ struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex {
|
||||
WrappedIDirect3DSwapChain9 *sub_swapchain[3] = { nullptr, nullptr, nullptr };
|
||||
FakeIDirect3DSwapChain9 *fake_sub_swapchain[3] = { nullptr, nullptr, nullptr };
|
||||
|
||||
UINT gfdm_logical_small_swapchain = 2;
|
||||
std::array<D3DPRESENT_PARAMETERS, 4> gfdm_logical_group_parameters {};
|
||||
bool gfdm_logical_group_parameters_valid = false;
|
||||
IDirect3D9 *gfdm_parent_d3d = nullptr;
|
||||
|
||||
std::mutex gfdm_recovery_mutex;
|
||||
std::array<D3DPRESENT_PARAMETERS, 2> gfdm_recovery_parameters {};
|
||||
std::array<D3DDISPLAYMODEEX, 2> gfdm_recovery_modes {};
|
||||
bool gfdm_recovery_armed = false;
|
||||
std::atomic_bool gfdm_recovery_consumed {false};
|
||||
const DWORD gfdm_device_creation_thread_id = GetCurrentThreadId();
|
||||
|
||||
IDirect3DVertexShader9 *vertex_shader = nullptr;
|
||||
};
|
||||
|
||||
@@ -59,7 +59,12 @@ HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::Present(const RECT *pSourceRe
|
||||
log_misc("graphics::d3d9", "FakeIDirect3DSwapChain9::Present");
|
||||
});
|
||||
|
||||
if (!is_hidden) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
present_count.fetch_add(1, std::memory_order_relaxed);
|
||||
return D3D_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetFrontBufferData(IDirect3DSurface9 *pDestSurface) {
|
||||
WRAP_VERBOSE;
|
||||
@@ -86,11 +91,32 @@ HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetBackBuffer(UINT iBackBuffe
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetRasterStatus(D3DRASTER_STATUS *pRasterStatus) {
|
||||
WRAP_VERBOSE;
|
||||
if (!is_hidden) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (pRasterStatus == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
*pRasterStatus = {};
|
||||
return D3D_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetDisplayMode(D3DDISPLAYMODE *pMode) {
|
||||
WRAP_VERBOSE;
|
||||
if (!is_hidden) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (pMode == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
pMode->Width = present_params.BackBufferWidth;
|
||||
pMode->Height = present_params.BackBufferHeight;
|
||||
pMode->RefreshRate = present_params.FullScreen_RefreshRateInHz;
|
||||
pMode->Format = present_params.BackBufferFormat;
|
||||
return D3D_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetDevice(IDirect3DDevice9 **ppDevice) {
|
||||
WRAP_VERBOSE;
|
||||
@@ -108,17 +134,46 @@ HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetPresentParameters(
|
||||
D3DPRESENT_PARAMETERS *pPresentationParameters)
|
||||
{
|
||||
WRAP_VERBOSE;
|
||||
if (!is_hidden) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (pPresentationParameters == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
*pPresentationParameters = present_params;
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
// IDirect3DSwapChain9Ex
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetLastPresentCount(UINT *pLastPresentCount) {
|
||||
assert(is_d3d9ex);
|
||||
if (!is_hidden) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (pLastPresentCount == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
*pLastPresentCount = present_count.load(std::memory_order_relaxed);
|
||||
return D3D_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetPresentStats(D3DPRESENTSTATS *pPresentationStatistics) {
|
||||
assert(is_d3d9ex);
|
||||
if (!is_hidden) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (pPresentationStatistics == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
*pPresentationStatistics = {};
|
||||
pPresentationStatistics->PresentCount =
|
||||
present_count.load(std::memory_order_relaxed);
|
||||
return D3D_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetDisplayModeEx(D3DDISPLAYMODEEX *pMode,
|
||||
D3DDISPLAYROTATION *pRotation)
|
||||
@@ -126,5 +181,22 @@ HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetDisplayModeEx(D3DDISPLAYMO
|
||||
WRAP_VERBOSE;
|
||||
|
||||
assert(is_d3d9ex);
|
||||
if (!is_hidden) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (pMode == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
pMode->Size = sizeof(*pMode);
|
||||
pMode->Width = present_params.BackBufferWidth;
|
||||
pMode->Height = present_params.BackBufferHeight;
|
||||
pMode->RefreshRate = present_params.FullScreen_RefreshRateInHz;
|
||||
pMode->Format = present_params.BackBufferFormat;
|
||||
pMode->ScanLineOrdering = D3DSCANLINEORDERING_PROGRESSIVE;
|
||||
if (pRotation != nullptr) {
|
||||
*pRotation = D3DDISPLAYROTATION_IDENTITY;
|
||||
}
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
@@ -6,14 +6,19 @@
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
#include <d3d9.h>
|
||||
|
||||
#include "util/logging.h"
|
||||
|
||||
struct FakeIDirect3DSwapChain9 : IDirect3DSwapChain9Ex {
|
||||
FakeIDirect3DSwapChain9(IDirect3DDevice9 *pDev, D3DPRESENT_PARAMETERS *present_params, bool is_d3d9ex) :
|
||||
pDev(pDev), is_d3d9ex(is_d3d9ex)
|
||||
FakeIDirect3DSwapChain9(
|
||||
IDirect3DDevice9 *pDev,
|
||||
D3DPRESENT_PARAMETERS *present_params,
|
||||
bool is_d3d9ex,
|
||||
bool is_hidden = false) :
|
||||
pDev(pDev), is_d3d9ex(is_d3d9ex), is_hidden(is_hidden)
|
||||
{
|
||||
// copy presentation parameters
|
||||
memcpy(&this->present_params, present_params, sizeof(this->present_params));
|
||||
@@ -78,8 +83,10 @@ struct FakeIDirect3DSwapChain9 : IDirect3DSwapChain9Ex {
|
||||
|
||||
IDirect3DDevice9 *const pDev;
|
||||
bool is_d3d9ex;
|
||||
bool is_hidden;
|
||||
|
||||
std::atomic<ULONG> ref_cnt = 1;
|
||||
std::atomic<UINT> present_count = 0;
|
||||
|
||||
D3DPRESENT_PARAMETERS present_params {};
|
||||
std::vector<IDirect3DSurface9 *> render_targets;
|
||||
|
||||
@@ -0,0 +1,715 @@
|
||||
#include "d3d9_gfdm.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
#include "d3d9_device.h"
|
||||
|
||||
bool gfdm_two_head_exclusive() {
|
||||
return games::gitadora::is_arena_model()
|
||||
&& games::gitadora::ARENA_TWO_HEAD_EXCLUSIVE
|
||||
&& !GRAPHICS_WINDOWED;
|
||||
}
|
||||
|
||||
HRESULT graphics_d3d9_gfdm_select_two_head_group_parameters(
|
||||
const D3DPRESENT_PARAMETERS *logical_presentation_parameters,
|
||||
const D3DDISPLAYMODEEX *logical_fullscreen_display_modes,
|
||||
D3DPRESENT_PARAMETERS *native_presentation_parameters,
|
||||
D3DDISPLAYMODEEX *native_fullscreen_display_modes,
|
||||
UINT *logical_small_swapchain,
|
||||
const char *operation)
|
||||
{
|
||||
if (!gfdm_two_head_exclusive()
|
||||
|| logical_presentation_parameters == nullptr
|
||||
|| native_presentation_parameters == nullptr
|
||||
|| logical_small_swapchain == nullptr)
|
||||
{
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
UINT small_slot = GFDM_LOGICAL_HEAD_COUNT;
|
||||
for (UINT i = 1; i < GFDM_LOGICAL_HEAD_COUNT; i++) {
|
||||
const auto ¶ms = logical_presentation_parameters[i];
|
||||
if (params.BackBufferWidth != GFDM_SMALL_WIDTH
|
||||
|| params.BackBufferHeight != GFDM_SMALL_HEIGHT)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (small_slot != GFDM_LOGICAL_HEAD_COUNT) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} found multiple SMALL slots: {} and {}",
|
||||
operation,
|
||||
small_slot,
|
||||
i);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
small_slot = i;
|
||||
}
|
||||
|
||||
if (small_slot == GFDM_LOGICAL_HEAD_COUNT) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} could not find an 800x1280 logical SMALL descriptor",
|
||||
operation);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (logical_fullscreen_display_modes != nullptr
|
||||
&& (logical_fullscreen_display_modes[small_slot].Width != GFDM_SMALL_WIDTH
|
||||
|| logical_fullscreen_display_modes[small_slot].Height != GFDM_SMALL_HEIGHT))
|
||||
{
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} SMALL slot {} mode is {}x{}, expected {}x{}",
|
||||
operation,
|
||||
small_slot,
|
||||
logical_fullscreen_display_modes[small_slot].Width,
|
||||
logical_fullscreen_display_modes[small_slot].Height,
|
||||
GFDM_SMALL_WIDTH,
|
||||
GFDM_SMALL_HEIGHT);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
native_presentation_parameters[0] = logical_presentation_parameters[0];
|
||||
native_presentation_parameters[1] = logical_presentation_parameters[small_slot];
|
||||
if (logical_fullscreen_display_modes != nullptr
|
||||
&& native_fullscreen_display_modes != nullptr)
|
||||
{
|
||||
native_fullscreen_display_modes[0] = logical_fullscreen_display_modes[0];
|
||||
native_fullscreen_display_modes[1] = logical_fullscreen_display_modes[small_slot];
|
||||
}
|
||||
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} selected logical SMALL slot {} -> native group head 1{}",
|
||||
operation,
|
||||
small_slot,
|
||||
logical_presentation_parameters[small_slot].hDeviceWindow == GFDM_SUBSCREEN_WINDOW
|
||||
? " (named SMALL host)"
|
||||
: "");
|
||||
*logical_small_swapchain = small_slot;
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
bool is_fake_subscreen_adapter(UINT adapter) {
|
||||
// MAIN and SMALL remain native D3D heads; LEFT/RIGHT are virtual.
|
||||
if (gfdm_two_head_exclusive()) {
|
||||
return adapter >= 2 && adapter < GFDM_LOGICAL_HEAD_COUNT;
|
||||
}
|
||||
|
||||
return FAKE_SUBSCREEN_ADAPTER && adapter > 0;
|
||||
}
|
||||
|
||||
void get_fake_subscreen_display_mode(
|
||||
UINT adapter,
|
||||
D3DDISPLAYMODE *mode)
|
||||
{
|
||||
(void) adapter;
|
||||
if (gfdm_two_head_exclusive()) {
|
||||
mode->Width = GFDM_SIDE_WIDTH;
|
||||
mode->Height = GFDM_SIDE_HEIGHT;
|
||||
} else {
|
||||
mode->Width = 1280;
|
||||
mode->Height = 800;
|
||||
}
|
||||
mode->RefreshRate = 60;
|
||||
mode->Format = D3DFMT_X8R8G8B8;
|
||||
}
|
||||
|
||||
void get_fake_subscreen_display_mode_ex(
|
||||
UINT adapter,
|
||||
D3DDISPLAYMODEEX *mode)
|
||||
{
|
||||
D3DDISPLAYMODE legacy_mode {};
|
||||
get_fake_subscreen_display_mode(adapter, &legacy_mode);
|
||||
|
||||
mode->Size = sizeof(*mode);
|
||||
mode->Width = legacy_mode.Width;
|
||||
mode->Height = legacy_mode.Height;
|
||||
mode->RefreshRate = legacy_mode.RefreshRate;
|
||||
mode->Format = legacy_mode.Format;
|
||||
mode->ScanLineOrdering = D3DSCANLINEORDERING_PROGRESSIVE;
|
||||
}
|
||||
|
||||
HRESULT graphics_d3d9_gfdm_remap_two_head_group_parameters(
|
||||
D3DPRESENT_PARAMETERS *presentation_parameters,
|
||||
D3DDISPLAYMODEEX *fullscreen_display_modes,
|
||||
const char *operation)
|
||||
{
|
||||
if (!gfdm_two_head_exclusive()) {
|
||||
return D3D_OK;
|
||||
}
|
||||
if (presentation_parameters == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
auto &secondary = presentation_parameters[1];
|
||||
if (secondary.BackBufferWidth != GFDM_SMALL_WIDTH
|
||||
|| secondary.BackBufferHeight != GFDM_SMALL_HEIGHT)
|
||||
{
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} expected physical SMALL {}x{} at group slot 1, got {}x{}",
|
||||
operation,
|
||||
GFDM_SMALL_WIDTH,
|
||||
GFDM_SMALL_HEIGHT,
|
||||
secondary.BackBufferWidth,
|
||||
secondary.BackBufferHeight);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (GFDM_SUBSCREEN_WINDOW == nullptr || !IsWindow(GFDM_SUBSCREEN_WINDOW)) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} cannot use the named SMALL window as physical group head 1",
|
||||
operation);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
secondary.hDeviceWindow = GFDM_SUBSCREEN_WINDOW;
|
||||
|
||||
if (fullscreen_display_modes != nullptr
|
||||
&& (fullscreen_display_modes[1].Width != GFDM_SMALL_WIDTH
|
||||
|| fullscreen_display_modes[1].Height != GFDM_SMALL_HEIGHT))
|
||||
{
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} SMALL head mode is {}x{}, expected 800x1280",
|
||||
operation,
|
||||
fullscreen_display_modes[1].Width,
|
||||
fullscreen_display_modes[1].Height);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
void graphics_d3d9_gfdm_align_two_head_refresh_to_desktop(
|
||||
IDirect3D9Ex *d3d,
|
||||
UINT master_adapter,
|
||||
D3DPRESENT_PARAMETERS *presentation_parameters,
|
||||
D3DDISPLAYMODEEX *fullscreen_display_modes,
|
||||
const char *operation)
|
||||
{
|
||||
if (d3d == nullptr
|
||||
|| presentation_parameters == nullptr
|
||||
|| fullscreen_display_modes == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (UINT head = 0; head < 2; head++) {
|
||||
const bool explicitly_forced =
|
||||
(head == 0 && GRAPHICS_FORCE_REFRESH > 0)
|
||||
|| (head == 1 && GRAPHICS_FORCE_REFRESH_SUB.has_value());
|
||||
if (explicitly_forced) {
|
||||
continue;
|
||||
}
|
||||
|
||||
D3DDISPLAYMODEEX desktop_mode {};
|
||||
desktop_mode.Size = sizeof(desktop_mode);
|
||||
D3DDISPLAYROTATION rotation = D3DDISPLAYROTATION_IDENTITY;
|
||||
const HRESULT result = d3d->GetAdapterDisplayModeEx(
|
||||
master_adapter + head,
|
||||
&desktop_mode,
|
||||
&rotation);
|
||||
if (FAILED(result)) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} could not query native head {} desktop refresh, hr={}",
|
||||
operation,
|
||||
head,
|
||||
FMT_HRESULT(result));
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto ¶meters = presentation_parameters[head];
|
||||
if (desktop_mode.Width != parameters.BackBufferWidth
|
||||
|| desktop_mode.Height != parameters.BackBufferHeight
|
||||
|| desktop_mode.Format != parameters.BackBufferFormat
|
||||
|| desktop_mode.RefreshRate == 0)
|
||||
{
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} head {} mode differs from the desktop",
|
||||
operation,
|
||||
head);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parameters.FullScreen_RefreshRateInHz != desktop_mode.RefreshRate
|
||||
|| fullscreen_display_modes[head].RefreshRate != desktop_mode.RefreshRate)
|
||||
{
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} head {} refresh {} / {} -> {} (rotation {})",
|
||||
operation,
|
||||
head,
|
||||
parameters.FullScreen_RefreshRateInHz,
|
||||
fullscreen_display_modes[head].RefreshRate,
|
||||
desktop_mode.RefreshRate,
|
||||
static_cast<UINT>(rotation));
|
||||
presentation_parameters[head].FullScreen_RefreshRateInHz =
|
||||
desktop_mode.RefreshRate;
|
||||
fullscreen_display_modes[head].RefreshRate =
|
||||
desktop_mode.RefreshRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT validate_gfdm_two_head_exclusive(
|
||||
IDirect3D9 *d3d,
|
||||
UINT adapter,
|
||||
D3DDEVTYPE device_type,
|
||||
DWORD behavior_flags,
|
||||
const D3DPRESENT_PARAMETERS *presentation_parameters)
|
||||
{
|
||||
if (!(behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head mode requires D3DCREATE_ADAPTERGROUP_DEVICE");
|
||||
return D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
if (adapter != 0) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive mode currently requires master adapter 0, game requested {}",
|
||||
adapter);
|
||||
return D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
if (presentation_parameters == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
const UINT native_adapter_count = d3d->GetAdapterCount();
|
||||
if (native_adapter_count != 2) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head mode requires two native D3D9 adapters; found {}",
|
||||
native_adapter_count);
|
||||
return D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
|
||||
D3DCAPS9 caps {};
|
||||
const HRESULT caps_result = d3d->GetDeviceCaps(adapter, device_type, &caps);
|
||||
if (FAILED(caps_result)) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive mode could not query native adapter caps, hr={}",
|
||||
FMT_HRESULT(caps_result));
|
||||
return caps_result;
|
||||
}
|
||||
if (caps.MasterAdapterOrdinal != adapter
|
||||
|| caps.AdapterOrdinalInGroup != 0
|
||||
|| caps.NumberOfAdaptersInGroup != 2)
|
||||
{
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"invalid adapter group: master={}, index={}, size={}",
|
||||
caps.MasterAdapterOrdinal,
|
||||
caps.AdapterOrdinalInGroup,
|
||||
caps.NumberOfAdaptersInGroup);
|
||||
return D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
|
||||
const auto &main = presentation_parameters[0];
|
||||
const auto &small_params = presentation_parameters[1];
|
||||
if (main.Windowed || small_params.Windowed) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive mode requires both group heads to be fullscreen");
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
if (small_params.BackBufferWidth != GFDM_SMALL_WIDTH
|
||||
|| small_params.BackBufferHeight != GFDM_SMALL_HEIGHT)
|
||||
{
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"SMALL head must be {}x{}; got {}x{}",
|
||||
GFDM_SMALL_WIDTH,
|
||||
GFDM_SMALL_HEIGHT,
|
||||
small_params.BackBufferWidth,
|
||||
small_params.BackBufferHeight);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
if (main.EnableAutoDepthStencil != small_params.EnableAutoDepthStencil) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head mode requires matching automatic depth-stencil settings");
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
if (main.EnableAutoDepthStencil
|
||||
&& (main.AutoDepthStencilFormat != small_params.AutoDepthStencilFormat
|
||||
|| main.BackBufferWidth != small_params.BackBufferWidth
|
||||
|| main.BackBufferHeight != small_params.BackBufferHeight
|
||||
|| main.BackBufferFormat != small_params.BackBufferFormat))
|
||||
{
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head mode cannot use unequal sizes with automatic depth-stencil");
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
static bool gfdm_find_alternate_small_mode(
|
||||
IDirect3D9Ex *d3d,
|
||||
UINT adapter,
|
||||
const D3DDISPLAYMODEEX &desired,
|
||||
D3DDISPLAYMODEEX *alternate)
|
||||
{
|
||||
if (d3d == nullptr || alternate == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
D3DDISPLAYMODEFILTER filter {};
|
||||
filter.Size = sizeof(filter);
|
||||
filter.Format = desired.Format;
|
||||
filter.ScanLineOrdering = D3DSCANLINEORDERING_UNKNOWN;
|
||||
const UINT count = d3d->GetAdapterModeCountEx(adapter, &filter);
|
||||
const bool portrait = desired.Width < desired.Height;
|
||||
|
||||
for (UINT index = 0; index < count; index++) {
|
||||
D3DDISPLAYMODEEX mode {};
|
||||
mode.Size = sizeof(mode);
|
||||
if (FAILED(d3d->EnumAdapterModesEx(adapter, &filter, index, &mode))
|
||||
|| mode.Width != desired.Width
|
||||
|| mode.Height != desired.Height
|
||||
|| mode.Format != desired.Format
|
||||
|| (mode.RefreshRate == desired.RefreshRate
|
||||
&& mode.ScanLineOrdering == desired.ScanLineOrdering))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
*alternate = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int preserve_orientation = 1; preserve_orientation >= 0; preserve_orientation--) {
|
||||
for (int preserve_refresh = 1; preserve_refresh >= 0; preserve_refresh--) {
|
||||
for (UINT index = 0; index < count; index++) {
|
||||
D3DDISPLAYMODEEX mode {};
|
||||
mode.Size = sizeof(mode);
|
||||
if (FAILED(d3d->EnumAdapterModesEx(adapter, &filter, index, &mode))
|
||||
|| (mode.Width == desired.Width && mode.Height == desired.Height)
|
||||
|| mode.Format != desired.Format)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (preserve_orientation && (mode.Width < mode.Height) != portrait) {
|
||||
continue;
|
||||
}
|
||||
if (preserve_refresh && mode.RefreshRate != desired.RefreshRate) {
|
||||
continue;
|
||||
}
|
||||
*alternate = mode;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool gfdm_wait_for_small_mode(
|
||||
HWND window,
|
||||
const D3DDISPLAYMODEEX &expected)
|
||||
{
|
||||
const HMONITOR monitor =
|
||||
window != nullptr ? MonitorFromWindow(window, MONITOR_DEFAULTTONULL) : nullptr;
|
||||
MONITORINFOEXA monitor_info {};
|
||||
monitor_info.cbSize = sizeof(monitor_info);
|
||||
if (monitor == nullptr || !GetMonitorInfoA(monitor, &monitor_info)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (UINT poll = 0; poll < 50; poll++) {
|
||||
DEVMODEA current {};
|
||||
current.dmSize = sizeof(current);
|
||||
if (EnumDisplaySettingsExA(
|
||||
monitor_info.szDevice,
|
||||
ENUM_CURRENT_SETTINGS,
|
||||
¤t,
|
||||
0)
|
||||
&& current.dmPelsWidth == expected.Width
|
||||
&& current.dmPelsHeight == expected.Height
|
||||
&& (expected.RefreshRate == 0
|
||||
|| current.dmDisplayFrequency == expected.RefreshRate))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Sleep(10);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
HRESULT graphics_d3d9_gfdm_recover_two_head_present_mode(
|
||||
IDirect3D9Ex *d3d,
|
||||
IDirect3DDevice9Ex *device,
|
||||
UINT master_adapter,
|
||||
const D3DPRESENT_PARAMETERS *desired_parameters,
|
||||
const D3DDISPLAYMODEEX *desired_modes)
|
||||
{
|
||||
if (d3d == nullptr
|
||||
|| device == nullptr
|
||||
|| desired_parameters == nullptr
|
||||
|| desired_modes == nullptr)
|
||||
{
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
D3DDISPLAYMODEEX alternate_small {};
|
||||
if (!gfdm_find_alternate_small_mode(
|
||||
d3d,
|
||||
master_adapter + 1,
|
||||
desired_modes[1],
|
||||
&alternate_small))
|
||||
{
|
||||
log_warning("graphics::d3d9", "no alternate SMALL mode is available for recovery");
|
||||
return D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
|
||||
D3DPRESENT_PARAMETERS temporary_parameters[2] {
|
||||
desired_parameters[0],
|
||||
desired_parameters[1]};
|
||||
D3DDISPLAYMODEEX temporary_modes[2] {
|
||||
desired_modes[0],
|
||||
desired_modes[1]};
|
||||
temporary_parameters[1].BackBufferWidth = alternate_small.Width;
|
||||
temporary_parameters[1].BackBufferHeight = alternate_small.Height;
|
||||
temporary_parameters[1].BackBufferFormat = alternate_small.Format;
|
||||
temporary_parameters[1].FullScreen_RefreshRateInHz = alternate_small.RefreshRate;
|
||||
temporary_modes[1] = alternate_small;
|
||||
|
||||
HRESULT temporary_result = device->ResetEx(temporary_parameters, temporary_modes);
|
||||
const bool temporary_settled =
|
||||
temporary_result == D3D_OK
|
||||
&& gfdm_wait_for_small_mode(desired_parameters[1].hDeviceWindow, alternate_small);
|
||||
|
||||
D3DPRESENT_PARAMETERS restore_parameters[2] {
|
||||
desired_parameters[0],
|
||||
desired_parameters[1]};
|
||||
D3DDISPLAYMODEEX restore_modes[2] {
|
||||
desired_modes[0],
|
||||
desired_modes[1]};
|
||||
HRESULT restore_result = device->ResetEx(restore_parameters, restore_modes);
|
||||
const bool restore_settled =
|
||||
restore_result == D3D_OK
|
||||
&& gfdm_wait_for_small_mode(desired_parameters[1].hDeviceWindow, desired_modes[1]);
|
||||
|
||||
if (temporary_result != D3D_OK) {
|
||||
return temporary_result;
|
||||
}
|
||||
if (restore_result != D3D_OK) {
|
||||
return restore_result;
|
||||
}
|
||||
if (!temporary_settled || !restore_settled) {
|
||||
return D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
return device->CheckDeviceState(desired_parameters[0].hDeviceWindow) == D3D_OK
|
||||
? D3D_OK
|
||||
: D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
|
||||
bool WrappedIDirect3DDevice9::is_gfdm_two_head_exclusive() const {
|
||||
return ::gfdm_two_head_exclusive();
|
||||
}
|
||||
|
||||
bool WrappedIDirect3DDevice9::is_gfdm_logical_small_swapchain(
|
||||
UINT swapchain) const
|
||||
{
|
||||
return swapchain == gfdm_logical_small_swapchain;
|
||||
}
|
||||
|
||||
bool WrappedIDirect3DDevice9::is_gfdm_logical_side_swapchain(
|
||||
UINT swapchain) const
|
||||
{
|
||||
return swapchain > 0
|
||||
&& swapchain < GFDM_LOGICAL_HEAD_COUNT
|
||||
&& swapchain != gfdm_logical_small_swapchain;
|
||||
}
|
||||
|
||||
size_t WrappedIDirect3DDevice9::gfdm_hidden_side_swapchain_slot(
|
||||
UINT swapchain) const
|
||||
{
|
||||
assert(is_gfdm_logical_side_swapchain(swapchain));
|
||||
return swapchain < gfdm_logical_small_swapchain
|
||||
? swapchain
|
||||
: swapchain - 1;
|
||||
}
|
||||
|
||||
void WrappedIDirect3DDevice9::set_gfdm_logical_group_parameters(
|
||||
const D3DPRESENT_PARAMETERS *presentation_parameters)
|
||||
{
|
||||
if (presentation_parameters == nullptr) {
|
||||
gfdm_logical_group_parameters_valid = false;
|
||||
return;
|
||||
}
|
||||
std::copy_n(
|
||||
presentation_parameters,
|
||||
gfdm_logical_group_parameters.size(),
|
||||
gfdm_logical_group_parameters.begin());
|
||||
gfdm_logical_group_parameters_valid = true;
|
||||
}
|
||||
|
||||
FakeIDirect3DSwapChain9 *
|
||||
WrappedIDirect3DDevice9::ensure_gfdm_hidden_side_swapchain(UINT iSwapChain) {
|
||||
assert(is_gfdm_logical_side_swapchain(iSwapChain));
|
||||
|
||||
const size_t index = gfdm_hidden_side_swapchain_slot(iSwapChain);
|
||||
if (fake_sub_swapchain[index] == nullptr) {
|
||||
D3DPRESENT_PARAMETERS params {};
|
||||
if (gfdm_logical_group_parameters_valid) {
|
||||
params = gfdm_logical_group_parameters[iSwapChain];
|
||||
} else {
|
||||
params.BackBufferWidth = 1080;
|
||||
params.BackBufferHeight = 1920;
|
||||
params.BackBufferFormat = D3DFMT_X8R8G8B8;
|
||||
params.BackBufferCount = 1;
|
||||
params.MultiSampleType = D3DMULTISAMPLE_NONE;
|
||||
params.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
params.Windowed = FALSE;
|
||||
params.FullScreen_RefreshRateInHz = 60;
|
||||
params.EnableAutoDepthStencil = FALSE;
|
||||
params.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
|
||||
}
|
||||
fake_sub_swapchain[index] = new FakeIDirect3DSwapChain9(
|
||||
this,
|
||||
¶ms,
|
||||
is_d3d9ex,
|
||||
true);
|
||||
}
|
||||
return fake_sub_swapchain[index];
|
||||
}
|
||||
|
||||
void WrappedIDirect3DDevice9::release_gfdm_hidden_side_swapchains() {
|
||||
for (UINT i = 1; i < GFDM_LOGICAL_HEAD_COUNT; i++) {
|
||||
if (!is_gfdm_logical_side_swapchain(i)) {
|
||||
continue;
|
||||
}
|
||||
const size_t index = gfdm_hidden_side_swapchain_slot(i);
|
||||
if (fake_sub_swapchain[index] != nullptr) {
|
||||
fake_sub_swapchain[index]->Release();
|
||||
fake_sub_swapchain[index] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WrappedIDirect3DDevice9::gfdm_disarm_present_mode_recovery() {
|
||||
std::lock_guard<std::mutex> lock(gfdm_recovery_mutex);
|
||||
gfdm_recovery_armed = false;
|
||||
gfdm_recovery_consumed.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
void WrappedIDirect3DDevice9::gfdm_arm_present_mode_recovery(
|
||||
const D3DPRESENT_PARAMETERS *native_presentation_parameters,
|
||||
const D3DDISPLAYMODEEX *native_fullscreen_display_modes)
|
||||
{
|
||||
if (native_presentation_parameters == nullptr
|
||||
|| native_fullscreen_display_modes == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(gfdm_recovery_mutex);
|
||||
gfdm_recovery_parameters[0] = native_presentation_parameters[0];
|
||||
gfdm_recovery_parameters[1] = native_presentation_parameters[1];
|
||||
gfdm_recovery_modes[0] = native_fullscreen_display_modes[0];
|
||||
gfdm_recovery_modes[1] = native_fullscreen_display_modes[1];
|
||||
if (gfdm_recovery_modes[0].Size == 0) {
|
||||
gfdm_recovery_modes[0].Size = sizeof(D3DDISPLAYMODEEX);
|
||||
}
|
||||
if (gfdm_recovery_modes[1].Size == 0) {
|
||||
gfdm_recovery_modes[1].Size = sizeof(D3DDISPLAYMODEEX);
|
||||
}
|
||||
gfdm_recovery_armed =
|
||||
!gfdm_recovery_parameters[0].Windowed
|
||||
&& !gfdm_recovery_parameters[1].Windowed
|
||||
&& IsWindow(gfdm_recovery_parameters[1].hDeviceWindow);
|
||||
gfdm_recovery_consumed.store(
|
||||
!gfdm_recovery_armed,
|
||||
std::memory_order_release);
|
||||
}
|
||||
|
||||
WrappedIDirect3DDevice9::GfdmPresentModeRecoveryResult
|
||||
WrappedIDirect3DDevice9::gfdm_recover_present_mode_change(
|
||||
HRESULT *failure_result)
|
||||
{
|
||||
if (failure_result != nullptr) {
|
||||
*failure_result = D3D_OK;
|
||||
}
|
||||
if (!is_gfdm_two_head_exclusive() || !is_d3d9ex
|
||||
|| gfdm_recovery_consumed.exchange(
|
||||
true,
|
||||
std::memory_order_acq_rel))
|
||||
{
|
||||
return GfdmPresentModeRecoveryResult::NotAttempted;
|
||||
}
|
||||
if (GetCurrentThreadId() != gfdm_device_creation_thread_id) {
|
||||
if (failure_result != nullptr) {
|
||||
*failure_result = D3DERR_INVALIDCALL;
|
||||
}
|
||||
return GfdmPresentModeRecoveryResult::Failed;
|
||||
}
|
||||
|
||||
std::array<D3DPRESENT_PARAMETERS, 2> parameters {};
|
||||
std::array<D3DDISPLAYMODEEX, 2> modes {};
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(gfdm_recovery_mutex);
|
||||
if (!gfdm_recovery_armed) {
|
||||
return GfdmPresentModeRecoveryResult::NotAttempted;
|
||||
}
|
||||
parameters = gfdm_recovery_parameters;
|
||||
modes = gfdm_recovery_modes;
|
||||
}
|
||||
|
||||
IDirect3D9 *raw_d3d = nullptr;
|
||||
HRESULT result = pReal->GetDirect3D(&raw_d3d);
|
||||
IDirect3D9Ex *raw_d3d_ex = nullptr;
|
||||
if (SUCCEEDED(result)) {
|
||||
result = raw_d3d != nullptr
|
||||
? raw_d3d->QueryInterface(IID_PPV_ARGS(&raw_d3d_ex))
|
||||
: E_FAIL;
|
||||
}
|
||||
D3DDEVICE_CREATION_PARAMETERS creation {};
|
||||
if (SUCCEEDED(result)) {
|
||||
result = pReal->GetCreationParameters(&creation);
|
||||
}
|
||||
if (FAILED(result) || raw_d3d_ex == nullptr) {
|
||||
if (raw_d3d_ex != nullptr) {
|
||||
raw_d3d_ex->Release();
|
||||
}
|
||||
if (raw_d3d != nullptr) {
|
||||
raw_d3d->Release();
|
||||
}
|
||||
if (failure_result != nullptr) {
|
||||
*failure_result = result;
|
||||
}
|
||||
return GfdmPresentModeRecoveryResult::Failed;
|
||||
}
|
||||
|
||||
result = graphics_d3d9_gfdm_recover_two_head_present_mode(
|
||||
raw_d3d_ex,
|
||||
static_cast<IDirect3DDevice9Ex *>(pReal),
|
||||
creation.AdapterOrdinal,
|
||||
parameters.data(),
|
||||
modes.data());
|
||||
raw_d3d_ex->Release();
|
||||
raw_d3d->Release();
|
||||
|
||||
if (result == D3DERR_NOTAVAILABLE) {
|
||||
return GfdmPresentModeRecoveryResult::NotAttempted;
|
||||
}
|
||||
if (FAILED(result)) {
|
||||
if (failure_result != nullptr) {
|
||||
*failure_result = result;
|
||||
}
|
||||
return GfdmPresentModeRecoveryResult::Failed;
|
||||
}
|
||||
return GfdmPresentModeRecoveryResult::ReadyToRetry;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <d3d9.h>
|
||||
|
||||
inline constexpr UINT GFDM_SIDE_WIDTH = 1080;
|
||||
inline constexpr UINT GFDM_SIDE_HEIGHT = 1920;
|
||||
inline constexpr UINT GFDM_SMALL_WIDTH = 800;
|
||||
inline constexpr UINT GFDM_SMALL_HEIGHT = 1280;
|
||||
inline constexpr UINT GFDM_LOGICAL_HEAD_COUNT = 4;
|
||||
inline constexpr UINT GFDM_NATIVE_SMALL_SWAPCHAIN = 1;
|
||||
|
||||
struct GfdmTwoHeadDeviceState {
|
||||
explicit GfdmTwoHeadDeviceState(
|
||||
D3DPRESENT_PARAMETERS *presentation_parameters,
|
||||
D3DDISPLAYMODEEX *fullscreen_display_modes,
|
||||
UINT logical_small_swapchain = 2)
|
||||
: presentation_parameters(presentation_parameters),
|
||||
fullscreen_display_modes(fullscreen_display_modes),
|
||||
logical_small_swapchain(logical_small_swapchain) {}
|
||||
|
||||
void use_native_parameters() {
|
||||
presentation_parameters = native_presentation_parameters.data();
|
||||
fullscreen_display_modes = source_fullscreen_display_modes != nullptr
|
||||
? native_fullscreen_display_modes.data()
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
std::array<D3DPRESENT_PARAMETERS, 2> native_presentation_parameters {};
|
||||
std::array<D3DDISPLAYMODEEX, 2> native_fullscreen_display_modes {};
|
||||
std::array<D3DPRESENT_PARAMETERS, 2> recovery_parameters {};
|
||||
std::array<D3DDISPLAYMODEEX, 2> recovery_modes {};
|
||||
D3DPRESENT_PARAMETERS *presentation_parameters;
|
||||
D3DDISPLAYMODEEX *fullscreen_display_modes;
|
||||
D3DDISPLAYMODEEX *const source_fullscreen_display_modes = fullscreen_display_modes;
|
||||
UINT logical_small_swapchain;
|
||||
bool recovery_candidate = false;
|
||||
};
|
||||
|
||||
bool gfdm_two_head_exclusive();
|
||||
bool is_fake_subscreen_adapter(UINT adapter);
|
||||
void get_fake_subscreen_display_mode(UINT adapter, D3DDISPLAYMODE *mode);
|
||||
void get_fake_subscreen_display_mode_ex(UINT adapter, D3DDISPLAYMODEEX *mode);
|
||||
|
||||
HRESULT graphics_d3d9_gfdm_select_two_head_group_parameters(
|
||||
const D3DPRESENT_PARAMETERS *logical_presentation_parameters,
|
||||
const D3DDISPLAYMODEEX *logical_fullscreen_display_modes,
|
||||
D3DPRESENT_PARAMETERS *native_presentation_parameters,
|
||||
D3DDISPLAYMODEEX *native_fullscreen_display_modes,
|
||||
UINT *logical_small_swapchain,
|
||||
const char *operation);
|
||||
|
||||
HRESULT graphics_d3d9_gfdm_remap_two_head_group_parameters(
|
||||
D3DPRESENT_PARAMETERS *presentation_parameters,
|
||||
D3DDISPLAYMODEEX *fullscreen_display_modes,
|
||||
const char *operation);
|
||||
|
||||
void graphics_d3d9_gfdm_align_two_head_refresh_to_desktop(
|
||||
IDirect3D9Ex *d3d,
|
||||
UINT master_adapter,
|
||||
D3DPRESENT_PARAMETERS *presentation_parameters,
|
||||
D3DDISPLAYMODEEX *fullscreen_display_modes,
|
||||
const char *operation);
|
||||
|
||||
HRESULT validate_gfdm_two_head_exclusive(
|
||||
IDirect3D9 *d3d,
|
||||
UINT adapter,
|
||||
D3DDEVTYPE device_type,
|
||||
DWORD behavior_flags,
|
||||
const D3DPRESENT_PARAMETERS *presentation_parameters);
|
||||
|
||||
HRESULT graphics_d3d9_gfdm_recover_two_head_present_mode(
|
||||
IDirect3D9Ex *d3d,
|
||||
IDirect3DDevice9Ex *device,
|
||||
UINT master_adapter,
|
||||
const D3DPRESENT_PARAMETERS *desired_parameters,
|
||||
const D3DDISPLAYMODEEX *desired_modes);
|
||||
@@ -26,7 +26,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::QueryInterface(REFIID riid
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (//riid == __uuidof(IUnknown) || Ignore IUnknown, it's often queried to test object equality between different interfaces
|
||||
if ((riid == IID_IUnknown && pDev->is_gfdm_two_head_exclusive()) ||
|
||||
riid == IID_IDirect3DSwapChain9 ||
|
||||
riid == IID_IDirect3DSwapChain9Ex)
|
||||
{
|
||||
@@ -89,7 +89,42 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::Present(const RECT *pSourc
|
||||
graphics_d3d9_on_present(pDev->hFocusWindow, pDev->pReal, pDev);
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags));
|
||||
HRESULT result = pReal->Present(
|
||||
pSourceRect,
|
||||
pDestRect,
|
||||
hDestWindowOverride,
|
||||
pDirtyRegion,
|
||||
dwFlags);
|
||||
|
||||
// Some drivers report S_PRESENT_MODE_CHANGED after Windows moves the portrait SMALL
|
||||
// head into the requested exclusive mode, leaving both group heads black. Toggle SMALL
|
||||
// through another supported mode and restore it once, then retry the interrupted MAIN
|
||||
// present. Recovery is restricted to the creation thread and consumes its armed state,
|
||||
// so subsequent presents cannot repeat the mode switch.
|
||||
if (pDev->is_gfdm_two_head_exclusive()
|
||||
&& native_group_head == NativeGroupHead::Main
|
||||
&& result == S_PRESENT_MODE_CHANGED)
|
||||
{
|
||||
HRESULT recovery_failure = D3D_OK;
|
||||
const auto recovery = pDev->gfdm_recover_present_mode_change(
|
||||
&recovery_failure);
|
||||
if (recovery
|
||||
== WrappedIDirect3DDevice9::GfdmPresentModeRecoveryResult::ReadyToRetry)
|
||||
{
|
||||
result = pReal->Present(
|
||||
pSourceRect,
|
||||
pDestRect,
|
||||
hDestWindowOverride,
|
||||
pDirtyRegion,
|
||||
dwFlags);
|
||||
} else if (recovery
|
||||
== WrappedIDirect3DDevice9::GfdmPresentModeRecoveryResult::Failed
|
||||
&& FAILED(recovery_failure))
|
||||
{
|
||||
result = recovery_failure;
|
||||
}
|
||||
}
|
||||
CHECK_RESULT(result);
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetFrontBufferData(IDirect3DSurface9 *pDestSurface) {
|
||||
CHECK_RESULT(pReal->GetFrontBufferData(pDestSurface));
|
||||
|
||||
@@ -5,8 +5,13 @@
|
||||
interface WrappedIDirect3DDevice9;
|
||||
|
||||
struct WrappedIDirect3DSwapChain9 : IDirect3DSwapChain9Ex {
|
||||
WrappedIDirect3DSwapChain9(WrappedIDirect3DDevice9 *dev, IDirect3DSwapChain9 *orig) :
|
||||
pDev(dev), pReal(orig), is_d3d9ex(false)
|
||||
enum class NativeGroupHead { Unknown, Main, Small };
|
||||
|
||||
WrappedIDirect3DSwapChain9(
|
||||
WrappedIDirect3DDevice9 *dev,
|
||||
IDirect3DSwapChain9 *orig,
|
||||
NativeGroupHead native_group_head = NativeGroupHead::Unknown) :
|
||||
pDev(dev), pReal(orig), is_d3d9ex(false), native_group_head(native_group_head)
|
||||
{
|
||||
IDirect3DSwapChain9Ex *swapchain = nullptr;
|
||||
|
||||
@@ -16,8 +21,11 @@ struct WrappedIDirect3DSwapChain9 : IDirect3DSwapChain9Ex {
|
||||
}
|
||||
}
|
||||
|
||||
WrappedIDirect3DSwapChain9(WrappedIDirect3DDevice9 *dev, IDirect3DSwapChain9Ex *orig) :
|
||||
pDev(dev), pReal(orig), is_d3d9ex(true)
|
||||
WrappedIDirect3DSwapChain9(
|
||||
WrappedIDirect3DDevice9 *dev,
|
||||
IDirect3DSwapChain9Ex *orig,
|
||||
NativeGroupHead native_group_head = NativeGroupHead::Unknown) :
|
||||
pDev(dev), pReal(orig), is_d3d9ex(true), native_group_head(native_group_head)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -53,6 +61,7 @@ struct WrappedIDirect3DSwapChain9 : IDirect3DSwapChain9Ex {
|
||||
|
||||
IDirect3DSwapChain9 *pReal;
|
||||
bool is_d3d9ex = false;
|
||||
NativeGroupHead native_group_head = NativeGroupHead::Unknown;
|
||||
|
||||
bool should_run_hooks = true;
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "games/ddr/ddr.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "games/popn/popn.h"
|
||||
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
|
||||
#include "hooks/graphics/backends/d3d11/d3d11_backend.h"
|
||||
@@ -44,6 +45,8 @@ HWND SDVX_SUBSCREEN_WINDOW = nullptr;
|
||||
HWND GFDM_SUBSCREEN_WINDOW = nullptr;
|
||||
static HWND GFDM_LEFT_WINDOW = nullptr;
|
||||
static HWND GFDM_RIGHT_WINDOW = nullptr;
|
||||
static HMONITOR GFDM_TWO_HEAD_SMALL_MONITOR = nullptr;
|
||||
static HWND GFDM_TWO_HEAD_SMALL_WINDOW = nullptr;
|
||||
HWND POPN_SUBSCREEN_WINDOW = nullptr;
|
||||
bool FAKE_SUBSCREEN_ADAPTER = false;
|
||||
|
||||
@@ -183,6 +186,13 @@ static bool is_gfdm_known_window(HWND hWnd) {
|
||||
return gitadora_window_name_for_hwnd(hWnd) != nullptr;
|
||||
}
|
||||
|
||||
static bool is_gfdm_two_head_small_window(HWND hWnd) {
|
||||
return games::gitadora::is_arena_model() &&
|
||||
games::gitadora::ARENA_TWO_HEAD_EXCLUSIVE &&
|
||||
hWnd != nullptr &&
|
||||
hWnd == GFDM_TWO_HEAD_SMALL_WINDOW;
|
||||
}
|
||||
|
||||
static bool gitadora_should_block_game_window_placement(HWND hWnd) {
|
||||
if (!GRAPHICS_WINDOWED || !games::gitadora::is_arena_model()) {
|
||||
return false;
|
||||
@@ -204,6 +214,95 @@ static void gitadora_remember_window(HWND hWnd, const std::string &window_name)
|
||||
}
|
||||
}
|
||||
|
||||
bool graphics_gitadora_prepare_two_head_device_window(
|
||||
HWND hWnd, HMONITOR target_monitor, UINT desired_width, UINT desired_height) {
|
||||
if (hWnd == nullptr || !IsWindow(hWnd)) {
|
||||
log_warning(
|
||||
"graphics",
|
||||
"two-head exclusive: native secondary device window is invalid: {}",
|
||||
fmt::ptr(hWnd));
|
||||
return false;
|
||||
}
|
||||
if (ShowWindow_orig == nullptr || SetWindowLongA_orig == nullptr ||
|
||||
SetWindowPos_orig == nullptr) {
|
||||
log_warning(
|
||||
"graphics",
|
||||
"two-head exclusive: window hooks are not initialized for {}",
|
||||
fmt::ptr(hWnd));
|
||||
return false;
|
||||
}
|
||||
|
||||
RECT rect_before {};
|
||||
GetWindowRect(hWnd, &rect_before);
|
||||
const DWORD style_before = static_cast<DWORD>(GetWindowLongA(hWnd, GWL_STYLE));
|
||||
const BOOL visible_before = IsWindowVisible(hWnd);
|
||||
|
||||
if (target_monitor != nullptr) {
|
||||
GFDM_TWO_HEAD_SMALL_MONITOR = target_monitor;
|
||||
}
|
||||
const HMONITOR monitor = target_monitor != nullptr
|
||||
? target_monitor
|
||||
: (GFDM_TWO_HEAD_SMALL_MONITOR != nullptr
|
||||
? GFDM_TWO_HEAD_SMALL_MONITOR
|
||||
: MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL));
|
||||
MONITORINFO monitor_info {};
|
||||
monitor_info.cbSize = sizeof(monitor_info);
|
||||
if (monitor == nullptr || !GetMonitorInfoA(monitor, &monitor_info)) {
|
||||
log_warning(
|
||||
"graphics",
|
||||
"two-head exclusive: could not find the SMALL monitor for {}",
|
||||
fmt::ptr(hWnd));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Arena requests D3DCREATE_NOWINDOWCHANGES, so place its SMALL window on
|
||||
// the second head before D3D9 takes ownership of it.
|
||||
const DWORD fullscreen_style = (style_before & ~WS_OVERLAPPEDWINDOW) | WS_POPUP;
|
||||
const bool style_changed = fullscreen_style != style_before;
|
||||
if (style_changed) {
|
||||
SetWindowLongA_orig(hWnd, GWL_STYLE, static_cast<LONG>(fullscreen_style));
|
||||
}
|
||||
const RECT &monitor_rect = monitor_info.rcMonitor;
|
||||
const int host_width = desired_width != 0
|
||||
? static_cast<int>(desired_width)
|
||||
: monitor_rect.right - monitor_rect.left;
|
||||
const int host_height = desired_height != 0
|
||||
? static_cast<int>(desired_height)
|
||||
: monitor_rect.bottom - monitor_rect.top;
|
||||
const bool rect_changed = rect_before.left != monitor_rect.left ||
|
||||
rect_before.top != monitor_rect.top ||
|
||||
rect_before.right != monitor_rect.left + host_width ||
|
||||
rect_before.bottom != monitor_rect.top + host_height;
|
||||
if ((style_changed || rect_changed) && !SetWindowPos_orig(
|
||||
hWnd,
|
||||
HWND_TOP,
|
||||
monitor_rect.left,
|
||||
monitor_rect.top,
|
||||
host_width,
|
||||
host_height,
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED)) {
|
||||
log_warning(
|
||||
"graphics",
|
||||
"two-head exclusive: failed to place SMALL window, error={}",
|
||||
GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!visible_before) {
|
||||
ShowWindow_orig(hWnd, SW_SHOWNOACTIVATE);
|
||||
}
|
||||
if (MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL) != monitor) {
|
||||
log_warning(
|
||||
"graphics",
|
||||
"two-head exclusive: SMALL window {} is on the wrong monitor",
|
||||
fmt::ptr(hWnd));
|
||||
return false;
|
||||
}
|
||||
|
||||
GFDM_TWO_HEAD_SMALL_WINDOW = hWnd;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool gitadora_should_allow_small_resize() {
|
||||
return GRAPHICS_WINDOWED &&
|
||||
games::gitadora::is_arena_model() &&
|
||||
@@ -618,10 +717,17 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
}
|
||||
|
||||
// only hook touch window if multiple windows are allowed
|
||||
if (is_gfdm_sub_window && GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||
if (gfdm_window_name == "LEFT" || gfdm_window_name == "RIGHT") {
|
||||
gitadora_remember_window(result, gfdm_window_name);
|
||||
}
|
||||
if (is_gfdm_sub_window &&
|
||||
((GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) ||
|
||||
games::gitadora::ARENA_TWO_HEAD_EXCLUSIVE)) {
|
||||
gitadora_remember_window(result, gfdm_window_name);
|
||||
if (GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
|
||||
}
|
||||
}
|
||||
if (is_gfdm_window && GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||
gitadora_force_window_style(result);
|
||||
}
|
||||
@@ -767,6 +873,10 @@ static BOOL WINAPI MoveWindow_hook(HWND hWnd, int X, int Y, int nWidth, int nHei
|
||||
nHeight,
|
||||
bRepaint);
|
||||
|
||||
if (is_gfdm_two_head_small_window(hWnd)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// sound voltex windowed mode adjustments
|
||||
if (GRAPHICS_WINDOWED && GRAPHICS_SDVX_FORCE_720 && avs::game::is_model("KFC")) {
|
||||
RECT rect {};
|
||||
@@ -874,6 +984,11 @@ static HCURSOR WINAPI SetCursor_hook(HCURSOR hCursor) {
|
||||
|
||||
static LONG WINAPI SetWindowLongA_hook(HWND hWnd, int nIndex, LONG dwNewLong) {
|
||||
|
||||
if (is_gfdm_two_head_small_window(hWnd) &&
|
||||
(nIndex == GWL_STYLE || nIndex == GWL_EXSTYLE)) {
|
||||
return GetWindowLongA(hWnd, nIndex);
|
||||
}
|
||||
|
||||
// DDR window style fix
|
||||
if (nIndex == GWL_STYLE && avs::game::is_model("MDX")) {
|
||||
dwNewLong |= WS_OVERLAPPEDWINDOW;
|
||||
@@ -894,6 +1009,11 @@ static LONG WINAPI SetWindowLongA_hook(HWND hWnd, int nIndex, LONG dwNewLong) {
|
||||
|
||||
static LONG WINAPI SetWindowLongW_hook(HWND hWnd, int nIndex, LONG dwNewLong) {
|
||||
|
||||
if (is_gfdm_two_head_small_window(hWnd) &&
|
||||
(nIndex == GWL_STYLE || nIndex == GWL_EXSTYLE)) {
|
||||
return GetWindowLongW(hWnd, nIndex);
|
||||
}
|
||||
|
||||
// DDR overlapped window fix
|
||||
if (nIndex == GWL_STYLE && avs::game::is_model("MDX")) {
|
||||
dwNewLong |= WS_OVERLAPPEDWINDOW;
|
||||
@@ -915,6 +1035,12 @@ static LONG WINAPI SetWindowLongW_hook(HWND hWnd, int nIndex, LONG dwNewLong) {
|
||||
static BOOL WINAPI SetWindowPos_hook(HWND hWnd, HWND hWndInsertAfter,
|
||||
int X, int Y, int cx, int cy, UINT uFlags) {
|
||||
|
||||
if (is_gfdm_two_head_small_window(hWnd) &&
|
||||
((uFlags & SWP_HIDEWINDOW) ||
|
||||
(uFlags & (SWP_NOMOVE | SWP_NOSIZE)) != (SWP_NOMOVE | SWP_NOSIZE))) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// windowed mode adjustments
|
||||
if (GRAPHICS_WINDOWED && (avs::game::is_model("LMA") || avs::game::is_model("MDX"))) {
|
||||
return TRUE;
|
||||
@@ -939,6 +1065,13 @@ static BOOL WINAPI SetWindowPos_hook(HWND hWnd, HWND hWndInsertAfter,
|
||||
}
|
||||
|
||||
static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
|
||||
if (is_gfdm_two_head_small_window(hWnd) &&
|
||||
(nCmdShow == SW_HIDE || nCmdShow == SW_MINIMIZE ||
|
||||
nCmdShow == SW_SHOWMINIMIZED || nCmdShow == SW_SHOWMINNOACTIVE ||
|
||||
nCmdShow == SW_FORCEMINIMIZE)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (games::gitadora::is_arena_model() &&
|
||||
GRAPHICS_PREVENT_SECONDARY_WINDOWS &&
|
||||
hWnd != GRAPHICS_HOOKED_WINDOW) {
|
||||
|
||||
@@ -119,6 +119,15 @@ extern bool D3D9_DEVICE_HOOK_DISABLE;
|
||||
|
||||
void graphics_init();
|
||||
void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParameters);
|
||||
// The native GITADORA two-head D3D9 group uses the game's named SMALL
|
||||
// device window for the native physical SMALL head. The game requests
|
||||
// D3DCREATE_NOWINDOWCHANGES, so this host must be made borderless and sized
|
||||
// to the requested native SMALL mode before the native fullscreen device is
|
||||
// created. During the adapter-group startup warmup, the monitor can briefly
|
||||
// report a different mode; preserve its origin but not that temporary size.
|
||||
bool graphics_gitadora_prepare_two_head_device_window(
|
||||
HWND hWnd, HMONITOR target_monitor = nullptr, UINT desired_width = 0,
|
||||
UINT desired_height = 0);
|
||||
void graphics_add_wnd_proc(WNDPROC wndProc);
|
||||
void graphics_remove_wnd_proc(WNDPROC wndProc);
|
||||
void graphics_hook_subscreen_window(HWND hWnd);
|
||||
|
||||
@@ -296,8 +296,9 @@ void hooks::lang::early_init() {
|
||||
&GetLocaleInfoA_orig);
|
||||
}
|
||||
|
||||
// for TDJ subscreen search keyboard
|
||||
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
|
||||
// for TDJ subscreen search keyboard and T44 narrow-string handling
|
||||
if ((avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) ||
|
||||
avs::game::is_model("T44")) {
|
||||
log_info("hooks::lang", "hooking IsDBCSLeadByte");
|
||||
detour::trampoline_try(
|
||||
"kernel32.dll",
|
||||
@@ -306,7 +307,7 @@ void hooks::lang::early_init() {
|
||||
&IsDBCSLeadByte_orig);
|
||||
}
|
||||
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
if (games::gitadora::is_arena_model() || avs::game::is_model("T44")) {
|
||||
log_info("hooks::lang", "hooking WideCharToMultiByte");
|
||||
detour::trampoline_try(
|
||||
"kernel32.dll",
|
||||
|
||||
@@ -644,6 +644,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::LoadGitaDoraModule].value_bool()) {
|
||||
attach_gitadora = true;
|
||||
}
|
||||
if (options[launcher::Options::GitaDoraDisableFrameLimiter].value_bool()) {
|
||||
games::gitadora::DISABLE_FRAME_LIMITER = true;
|
||||
}
|
||||
if (options[launcher::Options::GitaDoraCabinetType].is_active()) {
|
||||
games::gitadora::CAB_TYPE = options[launcher::Options::GitaDoraCabinetType].value_uint32();
|
||||
}
|
||||
@@ -1702,18 +1705,13 @@ int main_implementation(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// SDVXFullscreenLandscape disabled due to it messing with in-game camera angle
|
||||
// FullscreenOrientationFlip continues to live on, however.
|
||||
if (options[launcher::Options::SDVXFullscreenLandscape].value_bool() && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_fatal("launcher", "-sdvxlandscape removed due to a camera bug in SDVX!");
|
||||
if (options[launcher::Options::SDVXFullscreenLandscape].value_bool() && !GRAPHICS_WINDOWED) {
|
||||
#if SPICE64
|
||||
GRAPHICS_FS_ORIENTATION_SWAP = true;
|
||||
#else
|
||||
log_warning("launcher", "-sdvxlandscape is not supported in 32-bit SDVX, ignoring...");
|
||||
#endif
|
||||
}
|
||||
// if (options[launcher::Options::SDVXFullscreenLandscape].value_bool() && !GRAPHICS_WINDOWED) {
|
||||
// #if SPICE64
|
||||
// GRAPHICS_FS_ORIENTATION_SWAP = true;
|
||||
// #else
|
||||
// log_warning("launcher", "-sdvxlandscape is not supported in 32-bit SDVX, ignoring...");
|
||||
// #endif
|
||||
// }
|
||||
|
||||
if (options[launcher::Options::FullscreenOrientationFlip].value_bool() && !GRAPHICS_WINDOWED) {
|
||||
GRAPHICS_FS_ORIENTATION_SWAP = true;
|
||||
@@ -1855,6 +1853,13 @@ int main_implementation(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (check_dll("jubeat2019.dll")) {
|
||||
avs::game::DLL_NAME = "jubeat2019.dll";
|
||||
attach_io = true;
|
||||
attach_jb = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// RB
|
||||
if (check_dll("reflecbeat.dll")) {
|
||||
avs::game::DLL_NAME = "reflecbeat.dll";
|
||||
@@ -2283,7 +2288,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
games.push_back(new games::sdvx::SDVXGame());
|
||||
}
|
||||
if (attach_jb) {
|
||||
avs::core::set_default_heap_size("jubeat.dll");
|
||||
avs::core::set_default_heap_size(avs::game::DLL_NAME);
|
||||
games.push_back(new games::jb::JBGame());
|
||||
}
|
||||
if (attach_nostalgia) {
|
||||
|
||||
@@ -330,9 +330,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc =
|
||||
"Allows you to play portrait games in landscape (and vice versa) by transposing resolution and applying image scaling.\n\n"
|
||||
"Works great for some games, but can COMPLETELY BREAK other games - YMMV!\n\n"
|
||||
"Strongly consider combining this with -forceres option to render at monitor native resolution\n\n"
|
||||
"WARNING: for SDVX, this messes with camera angle for note lanes, causing it to be zoomed out, and causes performance issues "
|
||||
"with Live2D!",
|
||||
"Strongly consider combining this with -forceres option to render at monitor native resolution.\n\n"
|
||||
"For SDVX, use the dedicated -sdvxlandscape option.",
|
||||
.type = OptionType::Bool,
|
||||
.hidden = true,
|
||||
.category = "Full Screen Settings"
|
||||
@@ -1203,6 +1202,19 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.category = "Game Options",
|
||||
.quick_setting_category = "Game",
|
||||
},
|
||||
{
|
||||
.title = "GitaDora Disable Frame Limiter (EXPERIMENTAL)",
|
||||
.name = "gdnoframelimiter",
|
||||
.desc = "Only for GITADORA to GALAXY WAVE; not for XG series or Arena Model.\n\n"
|
||||
"Game engine's built-in frame limiter can result in unstable frame pacing "
|
||||
"(around 56-58 FPS) on modern Windows. Turning this on disables that and "
|
||||
"forces the game to rely on V-Sync instead, resulting in a stable FPS.\n\n"
|
||||
"Ensure your monitors are set to 60Hz.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "GitaDora",
|
||||
.category = "Game Options",
|
||||
.quick_setting_category = "Game",
|
||||
},
|
||||
{
|
||||
.title = "GitaDora Cabinet Type",
|
||||
.name = "gdcabtype",
|
||||
@@ -1299,7 +1311,9 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.name = "gdalayout",
|
||||
.desc = "For Arena Model: select how many windows to create.\n\n"
|
||||
"1 window: main only; use the subscreen overlay for SMALL.\n\n"
|
||||
"2 windows: main + SMALL. Note: currently only works for windowed mode, broken for fullscreen.\n\n"
|
||||
"2 windows: main + SMALL. Without -w, this uses native D3D9 adapter-group fullscreen "
|
||||
"with physical MAIN/SMALL and virtual LEFT/RIGHT. It requires two active displays on "
|
||||
"one adapter group. With -w, it uses borderless windows instead.\n\n"
|
||||
"4 windows: main + LEFT + RIGHT + SMALL. Fullscreen requires exactly 4 monitors in the "
|
||||
"right resolution; see wiki for details.",
|
||||
.type = OptionType::Enum,
|
||||
@@ -2165,7 +2179,6 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
{"25", "+25 dB"},
|
||||
{"30", "+30 dB"},
|
||||
},
|
||||
.quick_setting_category = "Audio",
|
||||
},
|
||||
{
|
||||
// AudioResample
|
||||
@@ -3012,17 +3025,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
},
|
||||
{
|
||||
// SDVXFullscreenLandscape
|
||||
.title = "(DISABLED) SDVX Landscape Mode (SDVX5+)",
|
||||
.title = "SDVX Full Screen Landscape Mode (SDVX5+, EXPERIMENTAL)",
|
||||
.name = "sdvxlandscape",
|
||||
.desc =
|
||||
"Option HIDDEN and DISABLED due to it affecting in-game camera angle for displaying lanes.\n\n"
|
||||
"Allows you to play in landscape by transposing resolution and applying image scaling.\n\n"
|
||||
"Works only for SDVX5 and above! This is identical to -forceresswap.\n\n"
|
||||
"Corrects the in-game perspective camera for the transposed display aspect ratio.\n\n"
|
||||
"Works only for SDVX5 and above.\n\n"
|
||||
"Will launch at 1080p by default; strongly consider combining this with -forceres option to render at monitor native resolution.",
|
||||
.type = OptionType::Bool,
|
||||
.hidden = true,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options",
|
||||
.quick_setting_category = "Game",
|
||||
},
|
||||
{
|
||||
// spice2x_EnableSMXStage
|
||||
|
||||
@@ -114,6 +114,7 @@ namespace launcher {
|
||||
LoadHelloPopnMusicModule,
|
||||
LoadGitaDoraModule,
|
||||
GitaDoraTwoChannelAudio,
|
||||
GitaDoraDisableFrameLimiter,
|
||||
GitaDoraCabinetType,
|
||||
GitaDoraLefty,
|
||||
GitaDoraWailHold,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "eamuse.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
|
||||
@@ -10,7 +11,6 @@
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
#include "overlay/overlay.h"
|
||||
@@ -335,7 +335,6 @@ void eamuse_coin_start_thread() {
|
||||
COIN_INPUT_THREAD = new std::thread([]() {
|
||||
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
|
||||
static bool COIN_INPUT_KEY_STATE = false;
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
while (COIN_INPUT_THREAD_ACTIVE) {
|
||||
|
||||
// check input key
|
||||
@@ -355,7 +354,7 @@ void eamuse_coin_start_thread() {
|
||||
}
|
||||
|
||||
// once every two frames
|
||||
timer.sleep(1000 / 30);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 30));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -409,7 +408,6 @@ void eamuse_pin_macro_start_thread() {
|
||||
size_t pin_index[2] = {PIN_MACRO_VALUES[0].length(), PIN_MACRO_VALUES[1].length()};
|
||||
|
||||
std::optional<uint8_t> active_unit = std::nullopt;
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
while (PIN_MACRO_THREAD_ACTIVE) {
|
||||
// wait for key press or auto-trigger
|
||||
@@ -438,7 +436,7 @@ void eamuse_pin_macro_start_thread() {
|
||||
}
|
||||
|
||||
if (!active_unit.has_value()) {
|
||||
timer.sleep(20);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -454,22 +452,22 @@ void eamuse_pin_macro_start_thread() {
|
||||
eamuse_set_keypad_overrides(unit, keypad_overrides[char_index]);
|
||||
}
|
||||
pin_index[unit]++;
|
||||
timer.sleep(100);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// clear
|
||||
eamuse_set_keypad_overrides(unit, 0);
|
||||
timer.sleep(50);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
// end of PIN
|
||||
if (pin_index[unit] == PIN_MACRO_VALUES[unit].length()) {
|
||||
active_unit = std::nullopt;
|
||||
timer.sleep(120);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(120));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
timer.sleep(200);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -675,7 +673,8 @@ void eamuse_autodetect_game() {
|
||||
eamuse_set_game("Beatmania IIDX");
|
||||
else if (avs::game::is_model("J44") ||
|
||||
avs::game::is_model("K44") ||
|
||||
avs::game::is_model("L44"))
|
||||
avs::game::is_model("L44") ||
|
||||
avs::game::is_model("T44"))
|
||||
eamuse_set_game("Jubeat");
|
||||
else if (avs::game::is_model("KDM"))
|
||||
eamuse_set_game("Dance Evolution");
|
||||
|
||||
@@ -495,6 +495,14 @@ namespace wintouchemu {
|
||||
if (hWnd != nullptr && USE_MOUSE) {
|
||||
bool button_pressed = get_async_primary_mouse();
|
||||
|
||||
// while the subscreen overlay cannot accept the mouse, treat the button as
|
||||
// released; this ends any active touch and prevents new mouse touch contacts
|
||||
if (overlay::OVERLAY &&
|
||||
overlay::OVERLAY->has_subscreen_touch_transform() &&
|
||||
!overlay::OVERLAY->accepts_subscreen_mouse_input()) {
|
||||
button_pressed = false;
|
||||
}
|
||||
|
||||
// if there was a touch event in the last 500 ms, don't insert new button presses
|
||||
if (button_pressed && (now - last_touch_event) < 500) {
|
||||
button_pressed = false;
|
||||
|
||||
@@ -908,6 +908,17 @@ bool overlay::SpiceOverlay::get_active() {
|
||||
return this->active;
|
||||
}
|
||||
|
||||
bool overlay::SpiceOverlay::is_subscreen_overlay_visible() {
|
||||
return this->get_active() &&
|
||||
this->window_sub != nullptr &&
|
||||
this->window_sub->get_active();
|
||||
}
|
||||
|
||||
bool overlay::SpiceOverlay::accepts_subscreen_mouse_input() {
|
||||
return this->is_subscreen_overlay_visible() &&
|
||||
this->window_sub->is_mouse_hovered();
|
||||
}
|
||||
|
||||
bool overlay::SpiceOverlay::has_focus() {
|
||||
return this->get_active() && ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow);
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ namespace overlay {
|
||||
void show_main_menu();
|
||||
void set_active(bool active);
|
||||
bool get_active();
|
||||
bool accepts_subscreen_mouse_input();
|
||||
bool has_focus();
|
||||
bool hotkeys_triggered();
|
||||
|
||||
@@ -143,20 +144,20 @@ namespace overlay {
|
||||
return this->hWnd;
|
||||
}
|
||||
|
||||
bool can_transform_touch_input() {
|
||||
return (this->subscreen_mouse_handler != nullptr);
|
||||
bool has_subscreen_touch_transform() {
|
||||
return (this->subscreen_touch_transform != nullptr);
|
||||
}
|
||||
|
||||
bool transform_touch_point(LONG *x, LONG *y) {
|
||||
if (this->get_active() && this->subscreen_mouse_handler) {
|
||||
return this->subscreen_mouse_handler(x, y);
|
||||
if (this->get_active() && this->subscreen_touch_transform) {
|
||||
return this->subscreen_touch_transform(x, y);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_subscreen_mouse_handler(const std::function<bool(LONG *, LONG *)> &f) {
|
||||
this->subscreen_mouse_handler = f;
|
||||
void set_subscreen_touch_transform(const std::function<bool(LONG *, LONG *)> &f) {
|
||||
this->subscreen_touch_transform = f;
|
||||
}
|
||||
|
||||
// renderer
|
||||
@@ -203,7 +204,7 @@ namespace overlay {
|
||||
|
||||
std::vector<std::unique_ptr<Window>> windows;
|
||||
|
||||
std::function<bool(LONG *, LONG *)> subscreen_mouse_handler = nullptr;
|
||||
std::function<bool(LONG *, LONG *)> subscreen_touch_transform = nullptr;
|
||||
|
||||
bool active = false;
|
||||
bool toggle_down = false;
|
||||
@@ -216,6 +217,7 @@ namespace overlay {
|
||||
// so render() never runs without a matching NewFrame.
|
||||
bool has_pending_frame = false;
|
||||
|
||||
bool is_subscreen_overlay_visible();
|
||||
void init();
|
||||
void add_font(const char* font, ImFontConfig* config, const ImWchar* glyphs);
|
||||
};
|
||||
|
||||
@@ -69,6 +69,7 @@ void overlay::Window::build() {
|
||||
|
||||
// check if active
|
||||
if (!this->active) {
|
||||
this->mouse_hovered = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -106,10 +107,18 @@ void overlay::Window::build() {
|
||||
// top spot. suppress that so only an explicit focus request (see below)
|
||||
// decides what comes to the front.
|
||||
const std::string window_id = this->title + "###" + to_string(this);
|
||||
if (ImGui::Begin(
|
||||
const bool build_contents = ImGui::Begin(
|
||||
window_id.c_str(),
|
||||
&this->active,
|
||||
this->flags | ImGuiWindowFlags_NoFocusOnAppearing)) {
|
||||
this->flags | ImGuiWindowFlags_NoFocusOnAppearing);
|
||||
|
||||
// keep the window hovered while its invisible input button is held during a drag
|
||||
this->mouse_hovered =
|
||||
ImGui::IsWindowHovered(
|
||||
ImGuiHoveredFlags_RootAndChildWindows |
|
||||
ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
|
||||
|
||||
if (build_contents) {
|
||||
|
||||
// window attributes - init_pos / init_size are only honored once
|
||||
// (ImGuiCond_Once), so compute them a single time instead of every frame
|
||||
@@ -154,6 +163,9 @@ void overlay::Window::build() {
|
||||
|
||||
} else {
|
||||
|
||||
// raw content does not have an ImGui window to hover
|
||||
this->mouse_hovered = false;
|
||||
|
||||
// add raw content
|
||||
this->build_content();
|
||||
}
|
||||
@@ -201,3 +213,7 @@ bool overlay::Window::get_active() {
|
||||
// now it depends on us
|
||||
return this->active;
|
||||
}
|
||||
|
||||
bool overlay::Window::is_mouse_hovered() {
|
||||
return this->mouse_hovered;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace overlay {
|
||||
void toggle_active();
|
||||
void set_active(bool active);
|
||||
bool get_active();
|
||||
bool is_mouse_hovered();
|
||||
|
||||
// raise this window to the top of the z-order on its next build. only
|
||||
// call in response to an explicit user action (keyboard toggle, UI
|
||||
@@ -37,6 +38,7 @@ namespace overlay {
|
||||
// set when the window transitions to visible so build() raises it to the
|
||||
// top of the z-order on the next frame
|
||||
bool request_focus = false;
|
||||
bool mouse_hovered = false;
|
||||
std::vector<Window*> children;
|
||||
|
||||
// settings
|
||||
|
||||
@@ -82,9 +82,14 @@ namespace overlay::windows {
|
||||
ImGui::Text("Rendering");
|
||||
|
||||
// Media Type Selector
|
||||
int selectedMediaTypeIndex = selectedCamera->m_selectedMediaTypeIndex;
|
||||
int selectedMediaTypeIndex = selectedCamera->GetSelectedMediaTypeIndex();
|
||||
auto numMediaTypes = selectedCamera->m_mediaTypeInfos.size();
|
||||
|
||||
if (numMediaTypes == 0) {
|
||||
ImGui::TextColored(ImVec4(1.f, 1.f, 0.f, 1.f), "%s", "No supported media types");
|
||||
return;
|
||||
}
|
||||
|
||||
auto selectedMediaTypeIndexChanged = ImGui::BeginCombo(
|
||||
"Media Type", selectedCamera->m_mediaTypeInfos.at(selectedMediaTypeIndex).description.c_str()
|
||||
);
|
||||
@@ -103,9 +108,9 @@ namespace overlay::windows {
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
if (selectedMediaTypeIndexChanged && selectedMediaTypeIndex != selectedCamera->m_selectedMediaTypeIndex) {
|
||||
if (selectedMediaTypeIndexChanged && selectedMediaTypeIndex != selectedCamera->GetSelectedMediaTypeIndex()) {
|
||||
selectedCamera->m_useAutoMediaType = false;
|
||||
selectedCamera->ChangeMediaType(selectedCamera->m_mediaTypeInfos.at(selectedMediaTypeIndex).p_mediaType);
|
||||
selectedCamera->RequestMediaType(selectedCamera->m_mediaTypeInfos.at(selectedMediaTypeIndex).p_mediaType);
|
||||
}
|
||||
|
||||
// Auto media type
|
||||
@@ -114,13 +119,14 @@ namespace overlay::windows {
|
||||
if (ImGui::Checkbox("Auto##MediaType", &isAutoMediaType)) {
|
||||
selectedCamera->m_useAutoMediaType = isAutoMediaType;
|
||||
if (isAutoMediaType) {
|
||||
selectedCamera->ChangeMediaType(selectedCamera->m_pAutoMediaType);
|
||||
selectedCamera->RequestMediaType(selectedCamera->m_pAutoMediaType);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw mode
|
||||
int selectedDrawModeIndex = selectedCamera->m_drawMode;
|
||||
if (ImGui::BeginCombo("Draw Mode", DRAW_MODE_LABELS[selectedCamera->m_drawMode].c_str())) {
|
||||
const auto drawMode = selectedCamera->m_drawMode.load();
|
||||
int selectedDrawModeIndex = drawMode;
|
||||
if (ImGui::BeginCombo("Draw Mode", DRAW_MODE_LABELS[drawMode].c_str())) {
|
||||
for (size_t i = 0; i < DRAW_MODE_SIZE; i++) {
|
||||
const bool is_selected = (selectedDrawModeIndex == (int) i);
|
||||
if (ImGui::Selectable(DRAW_MODE_LABELS[i].c_str(), is_selected)) {
|
||||
@@ -142,15 +148,25 @@ namespace overlay::windows {
|
||||
"Letterbox to 4:3: Like Letterbox, but target 4:3"
|
||||
);
|
||||
|
||||
if (selectedDrawModeIndex != selectedCamera->m_drawMode) {
|
||||
selectedCamera->m_drawMode = (LocalCameraDrawMode)selectedDrawModeIndex;
|
||||
if (selectedDrawModeIndex != selectedCamera->m_drawMode.load()) {
|
||||
selectedCamera->m_drawMode.store((LocalCameraDrawMode)selectedDrawModeIndex);
|
||||
selectedCamera->UpdateDrawRect();
|
||||
}
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Checkbox("Horizontal Flip", &selectedCamera->m_flipHorizontal);
|
||||
bool flipHorizontal = selectedCamera->m_flipHorizontal.load();
|
||||
if (ImGui::Checkbox("Horizontal Flip", &flipHorizontal)) {
|
||||
selectedCamera->m_flipHorizontal.store(flipHorizontal);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Vertical Flip", &selectedCamera->m_flipVertical);
|
||||
bool flipVertical = selectedCamera->m_flipVertical.load();
|
||||
if (ImGui::Checkbox("Vertical Flip", &flipVertical)) {
|
||||
selectedCamera->m_flipVertical.store(flipVertical);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Flips are done by the CPU and can be very expensive, potentially causing frame drops! "
|
||||
"Prefer to use camera's built-in flip if available.");
|
||||
|
||||
// Camera control parameters
|
||||
ImGui::Separator();
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace overlay::windows {
|
||||
this->texture_width = 0;
|
||||
this->texture_height = 0;
|
||||
|
||||
overlay->set_subscreen_mouse_handler([this](LONG *x, LONG *y) -> bool {
|
||||
overlay->set_subscreen_touch_transform([this](LONG *x, LONG *y) -> bool {
|
||||
// convert to normalized form (relative window coordinates 0.f-1.f)
|
||||
ImVec2 xy;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
@@ -103,6 +102,13 @@ namespace rawinput {
|
||||
std::vector<HIDTouchPoint> touch_points;
|
||||
};
|
||||
|
||||
struct HIDButtonInputGroup {
|
||||
USAGE usage_page;
|
||||
USHORT link_collection;
|
||||
std::vector<size_t> cap_indices;
|
||||
std::vector<USAGE> usages;
|
||||
};
|
||||
|
||||
struct DeviceHIDInfo {
|
||||
HANDLE handle;
|
||||
_HIDP_CAPS caps;
|
||||
@@ -125,11 +131,12 @@ namespace rawinput {
|
||||
|
||||
std::vector<std::vector<bool>> button_states;
|
||||
std::vector<std::vector<double>> button_up, button_down;
|
||||
std::vector<std::vector<uint8_t>> button_report_states;
|
||||
std::vector<std::vector<bool>> button_output_states;
|
||||
|
||||
// key: usage page, link collection
|
||||
// value: number of buttons for that key (combine ranges and nonranges)
|
||||
std::map<std::pair<USAGE, ULONG>, ULONG> button_usage_pages;
|
||||
std::vector<HIDButtonInputGroup> button_input_groups;
|
||||
std::vector<CHAR> output_report;
|
||||
std::vector<USAGE> button_output_usages;
|
||||
std::vector<USAGE> button_output_usages_off;
|
||||
|
||||
std::vector<float> value_states;
|
||||
std::vector<LONG> value_states_raw;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#include "rawinput.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdarg>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -9,7 +13,6 @@
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "external/robin_hood.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
@@ -169,9 +172,8 @@ void rawinput::RawInputManager::input_hwnd_create() {
|
||||
});
|
||||
|
||||
// wait for window creation being done
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
while (!this->input_hwnd) {
|
||||
timer.sleep(1);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,7 +441,9 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
||||
std::vector<std::string> button_caps_names;
|
||||
std::vector<std::vector<bool>> button_states;
|
||||
std::vector<std::vector<double>> button_up, button_down;
|
||||
std::map<std::pair<USAGE, ULONG>, ULONG> button_usage_pages;
|
||||
std::vector<std::vector<uint8_t>> button_report_states;
|
||||
std::vector<HIDButtonInputGroup> button_input_groups;
|
||||
std::map<std::pair<USAGE, ULONG>, size_t> button_input_group_indices;
|
||||
for (int button_cap_num = 0; button_cap_num < button_cap_length; button_cap_num++) {
|
||||
auto &button_caps = button_cap_data[button_cap_num];
|
||||
|
||||
@@ -458,7 +462,7 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
||||
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
|
||||
|
||||
// ignore bad ranges reported by bad devices
|
||||
if (button_count >= 0xffff) {
|
||||
if (button_count <= 0 || button_count >= 0xffff) {
|
||||
log_warning("rawinput", "skipping bad button cap range for device {}, range [{}, {}]",
|
||||
device_name,
|
||||
button_caps.Range.UsageMin,
|
||||
@@ -467,11 +471,30 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
||||
}
|
||||
|
||||
// fill vectors
|
||||
const size_t cap_index = button_caps_list.size();
|
||||
button_caps_list.emplace_back(button_caps);
|
||||
button_states.emplace_back(std::vector<bool>(static_cast<unsigned int>(button_count), false));
|
||||
button_up.emplace_back(std::vector<double>(static_cast<unsigned int>(button_count), 0.0));
|
||||
button_down.emplace_back(std::vector<double>(static_cast<unsigned int>(button_count), 0.0));
|
||||
button_usage_pages[std::make_pair(button_caps.UsagePage, button_caps.LinkCollection)] += button_count;
|
||||
button_report_states.emplace_back(static_cast<size_t>(button_count), 0);
|
||||
|
||||
const auto group_key = std::make_pair(
|
||||
button_caps.UsagePage,
|
||||
static_cast<ULONG>(button_caps.LinkCollection));
|
||||
auto [group_it, inserted] = button_input_group_indices.try_emplace(
|
||||
group_key,
|
||||
button_input_groups.size());
|
||||
if (inserted) {
|
||||
button_input_groups.emplace_back(HIDButtonInputGroup {
|
||||
.usage_page = button_caps.UsagePage,
|
||||
.link_collection = button_caps.LinkCollection,
|
||||
.cap_indices = {},
|
||||
.usages = {},
|
||||
});
|
||||
}
|
||||
auto &input_group = button_input_groups[group_it->second];
|
||||
input_group.cap_indices.push_back(cap_index);
|
||||
input_group.usages.resize(input_group.usages.size() + button_count);
|
||||
|
||||
// names
|
||||
for (USAGE usg = button_caps.Range.UsageMin; usg <= button_caps.Range.UsageMax; usg++) {
|
||||
@@ -512,7 +535,7 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
||||
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
|
||||
|
||||
// ignore bad ranges reported by bad devices
|
||||
if (button_count >= 0xffff) {
|
||||
if (button_count <= 0 || button_count >= 0xffff) {
|
||||
log_warning("rawinput", "skipping bad button output cap range for device {}, range [{}, {}]",
|
||||
device_name,
|
||||
button_caps.Range.UsageMin,
|
||||
@@ -800,8 +823,18 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
||||
new_device.hidInfo->button_states = std::move(button_states);
|
||||
new_device.hidInfo->button_up = std::move(button_up);
|
||||
new_device.hidInfo->button_down = std::move(button_down);
|
||||
new_device.hidInfo->button_report_states = std::move(button_report_states);
|
||||
new_device.hidInfo->button_output_states = std::move(button_output_states);
|
||||
new_device.hidInfo->button_usage_pages = std::move(button_usage_pages);
|
||||
new_device.hidInfo->button_input_groups = std::move(button_input_groups);
|
||||
new_device.hidInfo->output_report.resize(caps.OutputReportByteLength);
|
||||
size_t max_button_output_count = 0;
|
||||
for (const auto &button_output_state : new_device.hidInfo->button_output_states) {
|
||||
if (button_output_state.size() > max_button_output_count) {
|
||||
max_button_output_count = button_output_state.size();
|
||||
}
|
||||
}
|
||||
new_device.hidInfo->button_output_usages.reserve(max_button_output_count);
|
||||
new_device.hidInfo->button_output_usages_off.reserve(max_button_output_count);
|
||||
new_device.hidInfo->value_states = std::move(value_states);
|
||||
new_device.hidInfo->value_states_raw = std::move(value_states_raw);
|
||||
new_device.hidInfo->value_output_states = std::move(value_output_states);
|
||||
@@ -1573,11 +1606,17 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
||||
if (!data_size) {
|
||||
break;
|
||||
}
|
||||
std::shared_ptr<RAWINPUT> data((RAWINPUT *) new char[data_size]{});
|
||||
thread_local std::vector<RAWINPUT> data_buffer;
|
||||
const size_t data_count =
|
||||
(data_size + sizeof(RAWINPUT) - 1) / sizeof(RAWINPUT);
|
||||
if (data_buffer.size() < data_count) {
|
||||
data_buffer.resize(data_count);
|
||||
}
|
||||
auto data = data_buffer.data();
|
||||
if (GetRawInputData(
|
||||
(HRAWINPUT) lParam,
|
||||
RID_INPUT,
|
||||
data.get(),
|
||||
data,
|
||||
&data_size,
|
||||
sizeof(RAWINPUTHEADER)) != data_size) {
|
||||
break;
|
||||
@@ -1791,17 +1830,13 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
||||
+ (size_t) hid_report_index * data_hid.dwSizeHid;
|
||||
|
||||
// parse reports
|
||||
for (const auto &pair : device.hidInfo->button_usage_pages) {
|
||||
const auto usage_page = pair.first.first;
|
||||
const auto link_collection = pair.first.second;
|
||||
const auto button_count = pair.second;
|
||||
|
||||
ULONG usages_length = button_count;
|
||||
std::vector<USAGE> usages(static_cast<size_t>(usages_length));
|
||||
for (auto &input_group : device.hidInfo->button_input_groups) {
|
||||
auto &usages = input_group.usages;
|
||||
ULONG usages_length = static_cast<ULONG>(usages.size());
|
||||
if (HidP_GetUsages(
|
||||
HidP_Input,
|
||||
usage_page,
|
||||
link_collection,
|
||||
input_group.usage_page,
|
||||
input_group.link_collection,
|
||||
usages.data(),
|
||||
&usages_length,
|
||||
reinterpret_cast<PHIDP_PREPARSED_DATA>(device.hidInfo->preparsed_data.get()),
|
||||
@@ -1819,29 +1854,22 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
||||
// log_info(
|
||||
// "rawinput",
|
||||
// "processing HID input for device {}, usage page {:x} and link collection {:x} with {} buttons, got {} reports",
|
||||
// device.desc,
|
||||
// usage_page, link_collection, button_count, usages_length);
|
||||
// device.desc, input_group.usage_page,
|
||||
// input_group.link_collection, usages.size(), usages_length);
|
||||
|
||||
// buttons
|
||||
for (size_t cap_num = 0; cap_num < device.hidInfo->button_caps_list.size(); cap_num++) {
|
||||
for (const size_t cap_num : input_group.cap_indices) {
|
||||
auto &button_caps = device.hidInfo->button_caps_list[cap_num];
|
||||
auto &button_states = device.hidInfo->button_states[cap_num];
|
||||
auto &button_down = device.hidInfo->button_down[cap_num];
|
||||
auto &button_up = device.hidInfo->button_up[cap_num];
|
||||
|
||||
// is this the right usage page and link collection?
|
||||
if (button_caps.UsagePage != usage_page || button_caps.LinkCollection != link_collection) {
|
||||
continue;
|
||||
}
|
||||
auto &new_states = device.hidInfo->button_report_states[cap_num];
|
||||
|
||||
// get button count
|
||||
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
|
||||
if (button_count <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// update buttons
|
||||
std::vector<bool> new_states(button_count);
|
||||
std::fill(new_states.begin(), new_states.end(), 0);
|
||||
for (ULONG usage_num = 0; usage_num < usages_length; usage_num++) {
|
||||
if (usages[usage_num] < button_caps.Range.UsageMin ||
|
||||
usages[usage_num] > button_caps.Range.UsageMax) {
|
||||
@@ -1852,17 +1880,18 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
||||
|
||||
// guard against some buggy device sending an event for a usage below UsageMin
|
||||
if (usage < button_count) {
|
||||
new_states[usage] = true;
|
||||
new_states[usage] = 1;
|
||||
}
|
||||
}
|
||||
for (int button_num = 0; button_num < button_count; button_num++) {
|
||||
if (!new_states[button_num] && button_states[button_num]) {
|
||||
const bool new_state = new_states[button_num] != 0;
|
||||
if (!new_state && button_states[button_num]) {
|
||||
device.updated = true;
|
||||
button_states[button_num] = new_states[button_num];
|
||||
button_states[button_num] = new_state;
|
||||
button_down[button_num] = input_time;
|
||||
} else if (new_states[button_num] && !button_states[button_num]) {
|
||||
} else if (new_state && !button_states[button_num]) {
|
||||
device.updated = true;
|
||||
button_states[button_num] = new_states[button_num];
|
||||
button_states[button_num] = new_state;
|
||||
button_up[button_num] = input_time;
|
||||
}
|
||||
}
|
||||
@@ -2017,8 +2046,10 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
||||
switch (hid->driver) {
|
||||
case HIDDriver::Default: {
|
||||
|
||||
// allocate report
|
||||
CHAR *report_data = new CHAR[hid->caps.OutputReportByteLength] {};
|
||||
auto &report_data = hid->output_report;
|
||||
auto &usage_list = hid->button_output_usages;
|
||||
auto &usage_off_list = hid->button_output_usages_off;
|
||||
std::fill(report_data.begin(), report_data.end(), 0);
|
||||
|
||||
// set buttons
|
||||
for (size_t cap_no = 0; cap_no < hid->button_output_caps_list.size(); cap_no++) {
|
||||
@@ -2026,10 +2057,8 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
||||
auto &button_state_list = hid->button_output_states[cap_no];
|
||||
|
||||
// determine which buttons to turn on
|
||||
std::vector<USAGE> usage_list;
|
||||
std::vector<USAGE> usage_off_list;
|
||||
usage_list.reserve(button_state_list.size());
|
||||
usage_off_list.reserve(button_state_list.size());
|
||||
usage_list.clear();
|
||||
usage_off_list.clear();
|
||||
for (size_t state_no = 0; state_no < button_state_list.size(); state_no++) {
|
||||
if (button_state_list[state_no]) {
|
||||
usage_list.push_back(button_cap.Range.UsageMin + (USAGE) state_no);
|
||||
@@ -2044,15 +2073,18 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
||||
HidP_Output,
|
||||
button_cap.UsagePage,
|
||||
button_cap.LinkCollection,
|
||||
&usage_list[0],
|
||||
usage_list.data(),
|
||||
&usage_list_length,
|
||||
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
||||
report_data,
|
||||
report_data.data(),
|
||||
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
||||
|
||||
// flush report
|
||||
HidD_SetOutputReport(hid->handle, report_data, hid->caps.OutputReportByteLength);
|
||||
memset(report_data, 0, hid->caps.OutputReportByteLength);
|
||||
HidD_SetOutputReport(
|
||||
hid->handle,
|
||||
report_data.data(),
|
||||
hid->caps.OutputReportByteLength);
|
||||
std::fill(report_data.begin(), report_data.end(), 0);
|
||||
}
|
||||
|
||||
// clear the buttons
|
||||
@@ -2061,22 +2093,22 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
||||
HidP_Output,
|
||||
button_cap.UsagePage,
|
||||
button_cap.LinkCollection,
|
||||
&usage_off_list[0],
|
||||
usage_off_list.data(),
|
||||
&usage_off_list_length,
|
||||
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
||||
report_data,
|
||||
report_data.data(),
|
||||
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
||||
|
||||
// flush report
|
||||
DWORD written_bytes = 0;
|
||||
WriteFile(
|
||||
hid->handle,
|
||||
reinterpret_cast<void *>(report_data),
|
||||
report_data.data(),
|
||||
hid->caps.OutputReportByteLength,
|
||||
&written_bytes,
|
||||
nullptr
|
||||
);
|
||||
memset(report_data, 0, hid->caps.OutputReportByteLength);
|
||||
std::fill(report_data.begin(), report_data.end(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2105,19 +2137,19 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
||||
value_cap.NotRange.Usage,
|
||||
static_cast<ULONG>(usage_value),
|
||||
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
||||
report_data,
|
||||
report_data.data(),
|
||||
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
||||
|
||||
// flush report
|
||||
DWORD written_bytes = 0;
|
||||
WriteFile(
|
||||
hid->handle,
|
||||
reinterpret_cast<void *>(report_data),
|
||||
report_data.data(),
|
||||
hid->caps.OutputReportByteLength,
|
||||
&written_bytes,
|
||||
nullptr
|
||||
);
|
||||
memset(report_data, 0, hid->caps.OutputReportByteLength);
|
||||
std::fill(report_data.begin(), report_data.end(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2151,15 +2183,12 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
||||
DWORD written_bytes = 0;
|
||||
WriteFile(
|
||||
hid->handle,
|
||||
reinterpret_cast<void *>(report_data),
|
||||
report_data.data(),
|
||||
hid->caps.OutputReportByteLength,
|
||||
&written_bytes,
|
||||
nullptr
|
||||
);
|
||||
|
||||
// delete report
|
||||
delete[] report_data;
|
||||
|
||||
break;
|
||||
}
|
||||
case HIDDriver::PacDrive: {
|
||||
|
||||
@@ -319,7 +319,8 @@ namespace rawinput::touch {
|
||||
}
|
||||
|
||||
// iterate all input data and get touch points
|
||||
std::vector<HIDTouchPoint> touch_points;
|
||||
thread_local std::vector<HIDTouchPoint> touch_points;
|
||||
touch_points.clear();
|
||||
touch_points.reserve(touch.elements_x.size());
|
||||
for (size_t i = 0; i < touch.elements_x.size(); i++) {
|
||||
|
||||
@@ -424,9 +425,15 @@ namespace rawinput::touch {
|
||||
}
|
||||
|
||||
// process touch points
|
||||
std::vector<DWORD> touch_removes;
|
||||
std::vector<TouchPoint> touch_writes;
|
||||
std::vector<DWORD> touch_modifications;
|
||||
thread_local std::vector<DWORD> touch_removes;
|
||||
thread_local std::vector<TouchPoint> touch_writes;
|
||||
thread_local std::vector<DWORD> touch_modifications;
|
||||
touch_removes.clear();
|
||||
touch_writes.clear();
|
||||
touch_modifications.clear();
|
||||
touch_removes.reserve(touch.touch_points.size() + touch_points.size());
|
||||
touch_writes.reserve(touch_points.size());
|
||||
touch_modifications.reserve(touch_points.size());
|
||||
|
||||
// drop every touch point with the given id (marking it as released)
|
||||
auto remove_touch_point = [&] (DWORD id) {
|
||||
@@ -581,7 +588,9 @@ namespace rawinput::touch {
|
||||
auto deadline = get_system_milliseconds() - 50;
|
||||
|
||||
// check touch points
|
||||
std::vector<DWORD> touch_removes;
|
||||
thread_local std::vector<DWORD> touch_removes;
|
||||
touch_removes.clear();
|
||||
touch_removes.reserve(touch.touch_points.size());
|
||||
auto touch_it = touch.touch_points.begin();
|
||||
while (touch_it != touch.touch_points.end()) {
|
||||
auto &hid_tp = *touch_it;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#include "reader.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
#include "structuredmessage.h"
|
||||
|
||||
@@ -218,8 +218,6 @@ bool Reader::set_comm_state(DWORD BaudRate) {
|
||||
}
|
||||
|
||||
bool Reader::wait_for_handshake() {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// baud rates
|
||||
DWORD baud_rates[] = { CBR_57600, CBR_38400, CBR_19200, CBR_9600 };
|
||||
|
||||
@@ -270,7 +268,7 @@ bool Reader::wait_for_handshake() {
|
||||
}
|
||||
|
||||
// sleep
|
||||
timer.sleep(50);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
|
||||
log_warning("reader", "{}: no handshake received for {} baud", this->port, baud_rates[i]);
|
||||
@@ -407,7 +405,6 @@ void start_reader_thread(const std::string &port, int id) {
|
||||
READER_THREAD_RUNNING = true;
|
||||
READER_THREADS.push_back(new std::thread([port, id]() {
|
||||
log_info("reader", "{}: starting reader thread", port);
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
while (READER_THREAD_RUNNING) {
|
||||
|
||||
@@ -445,22 +442,21 @@ void start_reader_thread(const std::string &port, int id) {
|
||||
}
|
||||
|
||||
if (did_read_card) {
|
||||
timer.sleep(2500);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2500));
|
||||
}
|
||||
|
||||
timer.sleep(20);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
}
|
||||
}
|
||||
|
||||
// sleep between reader connection retries
|
||||
if (READER_THREAD_RUNNING)
|
||||
timer.sleep(5000);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
|
||||
}
|
||||
}));
|
||||
|
||||
// wait for thread to start
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
timer.sleep(10);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
void stop_reader_thread() {
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace nativetouch::inject {
|
||||
}
|
||||
|
||||
POINT transformed = *position;
|
||||
return transform::screen_to_game(window, &transformed);
|
||||
return transform::mouse_to_game(window, &transformed);
|
||||
}
|
||||
|
||||
// release the active injected contact and its window capture
|
||||
@@ -108,7 +108,7 @@ namespace nativetouch::inject {
|
||||
}
|
||||
|
||||
POINT transformed = position;
|
||||
if (!transform::screen_to_game(window, &transformed)) {
|
||||
if (!transform::mouse_to_game(window, &transformed)) {
|
||||
end_mouse_contact(window);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace nativetouch::transform {
|
||||
static bool has_active_overlay_transform() {
|
||||
return overlay::OVERLAY != nullptr &&
|
||||
overlay::OVERLAY->get_active() &&
|
||||
overlay::OVERLAY->can_transform_touch_input();
|
||||
overlay::OVERLAY->has_subscreen_touch_transform();
|
||||
}
|
||||
|
||||
static bool transform_overlay_touch_position(POINT *position) {
|
||||
@@ -120,6 +120,25 @@ namespace nativetouch::transform {
|
||||
return transform_overlay_touch_position(position);
|
||||
}
|
||||
|
||||
bool mouse_to_game(HWND window, POINT *position) {
|
||||
|
||||
// exception: iidx tdj dedicated subscreen window is allowed
|
||||
if (is_tdj_dedicated_subscreen(window)) {
|
||||
return screen_to_game(window, position);
|
||||
}
|
||||
|
||||
// if this game has a subscreen overlay that can transform touch input
|
||||
// but the window is hidden or not under the cursor, reject mouse-as-touch
|
||||
// (e.g., iidx/sdvx are rejected here, but nostalgia is allowed)
|
||||
if (overlay::OVERLAY != nullptr &&
|
||||
overlay::OVERLAY->has_subscreen_touch_transform() &&
|
||||
!overlay::OVERLAY->accepts_subscreen_mouse_input()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return screen_to_game(window, position);
|
||||
}
|
||||
|
||||
// route hardware screen coordinates through dedicated or overlay mapping and report the result
|
||||
Result hardware_to_game(POINT *position) {
|
||||
const auto dedicated_subscreen = is_tdj_dedicated_subscreen(TDJ_SUBSCREEN_WINDOW);
|
||||
|
||||
@@ -12,5 +12,6 @@ namespace nativetouch::transform {
|
||||
bool is_tdj_dedicated_subscreen(HWND window);
|
||||
bool game_to_screen(HWND window, POINT *position);
|
||||
bool screen_to_game(HWND window, POINT *position);
|
||||
bool mouse_to_game(HWND window, POINT *position);
|
||||
Result hardware_to_game(POINT *position);
|
||||
}
|
||||
|
||||
@@ -944,7 +944,7 @@ void touch_get_points(std::vector<TouchPoint> &touch_points, bool overlay) {
|
||||
if (!overlay &&
|
||||
overlay::OVERLAY &&
|
||||
overlay::OVERLAY->get_active() &&
|
||||
!overlay::OVERLAY->can_transform_touch_input() &&
|
||||
!overlay::OVERLAY->has_subscreen_touch_transform() &&
|
||||
ImGui::GetIO().WantCaptureMouse) {
|
||||
|
||||
return;
|
||||
@@ -971,7 +971,7 @@ void touch_get_events(std::vector<TouchEvent> &touch_events, bool overlay) {
|
||||
if (!overlay &&
|
||||
overlay::OVERLAY &&
|
||||
overlay::OVERLAY->get_active() &&
|
||||
!overlay::OVERLAY->can_transform_touch_input() &&
|
||||
!overlay::OVERLAY->has_subscreen_touch_transform() &&
|
||||
ImGui::GetIO().WantCaptureMouse) {
|
||||
|
||||
TOUCH_EVENTS.reset();
|
||||
|
||||
Reference in New Issue
Block a user