mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b930ad95f | |||
| 3c60f3966b | |||
| c59d399ab8 | |||
| f69e40fa26 | |||
| e9dcc5717e | |||
| 5bec3d5db7 | |||
| 6ec2b47345 |
@@ -523,6 +523,8 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
hooks/lang.cpp
|
||||
hooks/libraryhook.cpp
|
||||
hooks/networkhook.cpp
|
||||
hooks/icmphook_net.cpp
|
||||
hooks/icmphook_iphlpapi.cpp
|
||||
hooks/powrprof.cpp
|
||||
#hooks/rom.cpp
|
||||
hooks/setupapihook.cpp
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
@@ -8,11 +8,14 @@
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/cpuutils.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/sysutils.h"
|
||||
#include "util/utils.h"
|
||||
#include "hooks/setupapihook.h"
|
||||
|
||||
namespace games::gitadora {
|
||||
@@ -20,7 +23,6 @@ namespace games::gitadora {
|
||||
// settings
|
||||
bool TWOCHANNEL = false;
|
||||
std::optional<unsigned int> CAB_TYPE = std::nullopt;
|
||||
bool ARENA_SINGLE_WINDOW = false;
|
||||
bool P1_LEFTY = false;
|
||||
bool P2_LEFTY = false;
|
||||
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||
@@ -223,8 +225,7 @@ namespace games::gitadora {
|
||||
|
||||
// arena model launches a tiny window yet backbuffer at 4k, resulting in unusable overlay
|
||||
// force scaling to make things usable
|
||||
if (!overlay::UI_SCALE_PERCENT.has_value() && is_arena_model() &&
|
||||
GRAPHICS_WINDOWED && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
if (!overlay::UI_SCALE_PERCENT.has_value() && is_arena_model() && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
overlay::UI_SCALE_PERCENT = 250;
|
||||
}
|
||||
|
||||
@@ -237,9 +238,292 @@ namespace games::gitadora {
|
||||
} else {
|
||||
log_info("gitadora", "guitar pick SOCD algorithm: legacy");
|
||||
}
|
||||
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
if (is_arena_model() && !GRAPHICS_WINDOWED && !GRAPHICS_FORCE_SINGLE_ADAPTER) {
|
||||
const auto &monitors = sysutils::enumerate_monitors();
|
||||
const size_t active_count = monitors.size();
|
||||
log_info("gitadora", "arena model: detected {} active monitor(s)", active_count);
|
||||
if (active_count < 4) {
|
||||
log_info("gitadora", "arena model: enable single monitor mode due to insufficient monitors");
|
||||
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
||||
GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
static decltype(GetDisplayConfigBufferSizes) *GetDisplayConfigBufferSizes_orig = nullptr;
|
||||
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.
|
||||
static DISPLAYCONFIG_PATH_INFO real_primary_path = {};
|
||||
static DISPLAYCONFIG_MODE_INFO real_primary_modes[2] = {}; // [0]=source, [1]=target
|
||||
|
||||
// fake monitors appended after the real ones. the game classifies monitors
|
||||
// by outputTechnology + connectorInstance:
|
||||
// HDMI -> main 4k monitor (real primary)
|
||||
// DP connInstance 0 -> left
|
||||
// DP connInstance 1 -> right
|
||||
// DP connInstance 2 -> small (sub/touch)
|
||||
// ids are negated on the fake monitor headers so they can be distinguished
|
||||
// from real ones in DisplayConfigGetDeviceInfo.
|
||||
struct FakeMonitor {
|
||||
LONG id;
|
||||
int width;
|
||||
int height;
|
||||
int offset_x;
|
||||
int offset_y;
|
||||
UINT32 connector_instance;
|
||||
};
|
||||
|
||||
// ORDERING MATTERS: the d3d9 wrapper (FAKE_SUBSCREEN_ADAPTER) hands out
|
||||
// adapters as "\\.\DISPLAY_SPICE_FAKE_{N}" for N=1,2,3. The game maps each
|
||||
// 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[] = {
|
||||
{ 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));
|
||||
|
||||
// call QueryDisplayConfig once, keep only the primary monitor's path and its
|
||||
// two referenced modes (source + target). modeInfoIdx values are rewritten to
|
||||
// 0 and 1 so the cache is self-consistent.
|
||||
static void cache_primary_monitor_info() {
|
||||
UINT32 path_count = 0;
|
||||
UINT32 mode_count = 0;
|
||||
if (GetDisplayConfigBufferSizes_orig(
|
||||
QDC_ONLY_ACTIVE_PATHS, &path_count, &mode_count) != ERROR_SUCCESS) {
|
||||
log_fatal("gitadora", "cache_primary_monitor_info: GetDisplayConfigBufferSizes failed");
|
||||
}
|
||||
|
||||
std::vector<DISPLAYCONFIG_PATH_INFO> all_paths(path_count);
|
||||
std::vector<DISPLAYCONFIG_MODE_INFO> all_modes(mode_count);
|
||||
if (QueryDisplayConfig_orig(
|
||||
QDC_ONLY_ACTIVE_PATHS,
|
||||
&path_count, all_paths.data(),
|
||||
&mode_count, all_modes.data(),
|
||||
nullptr) != ERROR_SUCCESS) {
|
||||
log_fatal("gitadora", "cache_primary_monitor_info: QueryDisplayConfig failed");
|
||||
}
|
||||
all_paths.resize(path_count);
|
||||
all_modes.resize(mode_count);
|
||||
|
||||
// pick the primary monitor: source mode at (0, 0)
|
||||
auto primary = std::find_if(all_paths.begin(), all_paths.end(),
|
||||
[&](const auto &p) {
|
||||
const auto idx = p.sourceInfo.modeInfoIdx;
|
||||
return idx < all_modes.size() &&
|
||||
all_modes[idx].infoType == DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE &&
|
||||
all_modes[idx].sourceMode.position.x == 0 &&
|
||||
all_modes[idx].sourceMode.position.y == 0;
|
||||
});
|
||||
if (primary == all_paths.end()) {
|
||||
log_fatal("gitadora", "cache_primary_monitor_info: no primary monitor found");
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
log_info("gitadora", "cache_primary_monitor_info: cached primary monitor");
|
||||
}
|
||||
|
||||
static
|
||||
LONG
|
||||
WINAPI
|
||||
GetDisplayConfigBufferSizes_hook(
|
||||
UINT32 Flags,
|
||||
UINT32 *pNumPathArrayElements,
|
||||
UINT32 *pNumModeInfoArrayElements)
|
||||
{
|
||||
// populate cached primary real monitor on the first call
|
||||
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;
|
||||
|
||||
log_info(
|
||||
"gitadora",
|
||||
"GetDisplayConfigBufferSizes: 1 real path + {} fake monitor(s)",
|
||||
FAKE_MONITOR_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).
|
||||
static void insert_fake_monitor(
|
||||
DISPLAYCONFIG_PATH_INFO *paths,
|
||||
DISPLAYCONFIG_MODE_INFO *modes,
|
||||
UINT32 i)
|
||||
{
|
||||
const FakeMonitor &m = FAKE_MONITORS[i];
|
||||
const UINT32 src_idx = 2 + i * 2;
|
||||
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] = {
|
||||
.sourceInfo = {
|
||||
.adapterId = adapter_id,
|
||||
.id = uid,
|
||||
.modeInfoIdx = src_idx,
|
||||
.statusFlags = DISPLAYCONFIG_SOURCE_IN_USE,
|
||||
},
|
||||
.targetInfo = {
|
||||
.adapterId = adapter_id,
|
||||
.id = uid,
|
||||
.modeInfoIdx = tgt_idx,
|
||||
.outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL,
|
||||
.rotation = DISPLAYCONFIG_ROTATION_IDENTITY,
|
||||
.scaling = DISPLAYCONFIG_SCALING_IDENTITY,
|
||||
.refreshRate = { .Numerator = 60000, .Denominator = 1000 },
|
||||
.scanLineOrdering = DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE,
|
||||
.targetAvailable = TRUE,
|
||||
.statusFlags = DISPLAYCONFIG_TARGET_IN_USE,
|
||||
},
|
||||
.flags = DISPLAYCONFIG_PATH_ACTIVE,
|
||||
};
|
||||
|
||||
modes[src_idx] = {
|
||||
.infoType = DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE,
|
||||
.id = uid,
|
||||
.adapterId = adapter_id,
|
||||
.sourceMode = {
|
||||
.width = static_cast<UINT32>(m.width),
|
||||
.height = static_cast<UINT32>(m.height),
|
||||
.pixelFormat = DISPLAYCONFIG_PIXELFORMAT_32BPP,
|
||||
.position = { .x = m.offset_x, .y = m.offset_y },
|
||||
},
|
||||
};
|
||||
|
||||
modes[tgt_idx] = {
|
||||
.infoType = DISPLAYCONFIG_MODE_INFO_TYPE_TARGET,
|
||||
.id = uid,
|
||||
.adapterId = adapter_id,
|
||||
.targetMode = {},
|
||||
};
|
||||
|
||||
log_misc(
|
||||
"gitadora",
|
||||
"inserted fake monitor: id={}, width={}, height={}, offset_x={}, offset_y={}",
|
||||
m.id, m.width, m.height, m.offset_x, m.offset_y);
|
||||
}
|
||||
|
||||
static
|
||||
LONG
|
||||
WINAPI
|
||||
QueryDisplayConfig_hook(
|
||||
UINT32 flags,
|
||||
UINT32* numPathArrayElements,
|
||||
DISPLAYCONFIG_PATH_INFO* pathArray,
|
||||
UINT32* numModeInfoArrayElements,
|
||||
DISPLAYCONFIG_MODE_INFO* modeInfoArray,
|
||||
DISPLAYCONFIG_TOPOLOGY_ID* currentTopologyId)
|
||||
{
|
||||
// copy cached primary real monitor into caller buffers at index 0
|
||||
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 (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);
|
||||
}
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static
|
||||
LONG
|
||||
WINAPI
|
||||
DisplayConfigGetDeviceInfo_hook(DISPLAYCONFIG_DEVICE_INFO_HEADER* requestPacket)
|
||||
{
|
||||
if (requestPacket == nullptr) {
|
||||
return DisplayConfigGetDeviceInfo_orig(requestPacket);
|
||||
}
|
||||
|
||||
// handle fake monitors (negative id) directly without calling orig
|
||||
const auto id = static_cast<int>(requestPacket->id);
|
||||
if (id < 0) {
|
||||
if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) {
|
||||
const auto sourceName = reinterpret_cast<DISPLAYCONFIG_SOURCE_DEVICE_NAME*>(requestPacket);
|
||||
// name must match WrappedIDirect3D9::GetAdapterIdentifier
|
||||
const std::string adapter_name = fmt::format("\\\\.\\DISPLAY_SPICE_FAKE_{}", -id);
|
||||
wcscpy(sourceName->viewGdiDeviceName, s2ws(adapter_name).c_str());
|
||||
log_misc("gitadora",
|
||||
"DisplayConfigGetDeviceInfo: fake source id={} name={}", id, adapter_name);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) {
|
||||
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) {
|
||||
if (f.id == fake_id) {
|
||||
conn_inst = f.connector_instance;
|
||||
break;
|
||||
}
|
||||
}
|
||||
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL;
|
||||
targetName->connectorInstance = conn_inst;
|
||||
wcscpy(targetName->monitorFriendlyDeviceName, L"Spice Fake Monitor");
|
||||
wcscpy(targetName->monitorDevicePath, L"\\\\?\\SpiceFakeMonitor");
|
||||
log_misc("gitadora",
|
||||
"DisplayConfigGetDeviceInfo: fake target id={} -> DP connInst {}",
|
||||
id, targetName->connectorInstance);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
const auto ret = DisplayConfigGetDeviceInfo_orig(requestPacket);
|
||||
if (ret != ERROR_SUCCESS ||
|
||||
requestPacket->type != DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// override the cached primary real monitor target info to look like HDMI/0
|
||||
const auto targetName = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
|
||||
const auto& target = real_primary_path.targetInfo;
|
||||
if (target.id == targetName->header.id &&
|
||||
target.adapterId.HighPart == targetName->header.adapterId.HighPart &&
|
||||
target.adapterId.LowPart == targetName->header.adapterId.LowPart)
|
||||
{
|
||||
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
|
||||
targetName->connectorInstance = 0;
|
||||
log_info("gitadora",
|
||||
"overriding primary monitor (id={}) to pretend to be HDMI",
|
||||
targetName->header.id);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void GitaDoraGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
@@ -296,38 +580,62 @@ namespace games::gitadora {
|
||||
|
||||
// volume change prevention
|
||||
hooks::audio::mme::init(avs::game::DLL_INSTANCE);
|
||||
|
||||
// touch hook
|
||||
if (ARENA_SINGLE_WINDOW) {
|
||||
|
||||
// monitor/touch hooks (windowed or full screen)
|
||||
if (GRAPHICS_FORCE_SINGLE_ADAPTER || GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
||||
// enable touch hook for subscreen overlay
|
||||
wintouchemu::FORCE = true;
|
||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
HMODULE gdme_module = libutils::try_module("libgdme.dll");
|
||||
#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
|
||||
GetDisplayConfigBufferSizes_orig =
|
||||
detour::iat_try("GetDisplayConfigBufferSizes",
|
||||
GetDisplayConfigBufferSizes_hook, avs::game::DLL_INSTANCE);
|
||||
QueryDisplayConfig_orig =
|
||||
detour::iat_try("QueryDisplayConfig",
|
||||
QueryDisplayConfig_hook, avs::game::DLL_INSTANCE);
|
||||
DisplayConfigGetDeviceInfo_orig =
|
||||
detour::iat_try("DisplayConfigGetDeviceInfo",
|
||||
DisplayConfigGetDeviceInfo_hook, avs::game::DLL_INSTANCE);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// window patch
|
||||
if (GRAPHICS_WINDOWED && !replace_pattern(
|
||||
gdme_module,
|
||||
"754185ED753D8B4118BF0000CB02",
|
||||
"9090????9090??????????????12", 0, 0))
|
||||
{
|
||||
log_warning("gitadora", "windowed mode failed");
|
||||
if (!is_arena_model()) {
|
||||
HMODULE gdme_module = libutils::try_module("libgdme.dll");
|
||||
if (GRAPHICS_WINDOWED && !replace_pattern(
|
||||
gdme_module,
|
||||
"754185ED753D8B4118BF0000CB02",
|
||||
"9090????9090??????????????12", 0, 0)) {
|
||||
log_warning("gitadora", "windowed mode failed");
|
||||
}
|
||||
}
|
||||
|
||||
HMODULE bmsd_engine_module = libutils::try_module("libbmsd-engine.dll");
|
||||
HMODULE bmsd_module = libutils::try_module("libbmsd.dll");
|
||||
|
||||
// two channel mod
|
||||
if (TWOCHANNEL) {
|
||||
bmsd2_boot_orig = detour::iat_try("bmsd2_boot", bmsd2_boot_hook, bmsd_module);
|
||||
if (is_arena_model()) {
|
||||
log_warning("gitadora", "two channel audio (-2ch) is not supported on Arena Model - use a patch instead");
|
||||
deferredlogs::defer_error_messages({
|
||||
"two channel audio (-2ch) is not supported on Arena Model - use a patch instead",
|
||||
});
|
||||
|
||||
if (!(replace_pattern(bmsd_engine_module, "33000000488D", "03??????????", 0, 0) ||
|
||||
replace_pattern(bmsd_engine_module, "330000000F10", "03??????????", 0, 0)))
|
||||
{
|
||||
log_warning("gitadora", "two channel mode failed");
|
||||
} else {
|
||||
HMODULE bmsd_engine_module = libutils::try_module("libbmsd-engine.dll");
|
||||
HMODULE bmsd_module = libutils::try_module("libbmsd.dll");
|
||||
|
||||
bmsd2_boot_orig = detour::iat_try("bmsd2_boot", bmsd2_boot_hook, bmsd_module);
|
||||
if (!(replace_pattern(bmsd_engine_module, "33000000488D", "03??????????", 0, 0) ||
|
||||
replace_pattern(bmsd_engine_module, "330000000F10", "03??????????", 0, 0))) {
|
||||
log_warning("gitadora", "two channel mode failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace games::gitadora {
|
||||
// settings
|
||||
extern bool TWOCHANNEL;
|
||||
extern std::optional<unsigned int> CAB_TYPE;
|
||||
extern bool ARENA_SINGLE_WINDOW;
|
||||
extern bool P1_LEFTY;
|
||||
extern bool P2_LEFTY;
|
||||
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||
|
||||
@@ -261,7 +261,7 @@ namespace games::popn {
|
||||
} else if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) {
|
||||
const auto source = reinterpret_cast<DISPLAYCONFIG_SOURCE_DEVICE_NAME*>(requestPacket);
|
||||
// value must match WrappedIDirect3D9::GetAdapterIdentifier
|
||||
wcscpy(source->viewGdiDeviceName, L"\\\\.\\DISPLAY_SPICE_FAKE");
|
||||
wcscpy(source->viewGdiDeviceName, L"\\\\.\\DISPLAY_SPICE_FAKE_1");
|
||||
} else {
|
||||
log_fatal(
|
||||
"popn",
|
||||
|
||||
@@ -6,27 +6,32 @@
|
||||
#include "util/detour.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/logging.h"
|
||||
#include "cfg/configurator.h"
|
||||
|
||||
#include "touch.h"
|
||||
|
||||
namespace games::rb {
|
||||
uint16_t TOUCH_POLL_RATE = 0;
|
||||
}
|
||||
|
||||
static decltype(SleepEx) *SleepEx_orig;
|
||||
|
||||
static DWORD WINAPI SleepEx_hook(DWORD dwMilliseconds, BOOL bAltertable) {
|
||||
static DWORD WINAPI SleepEx_hook(DWORD dwMilliseconds, BOOL bAlertable) {
|
||||
|
||||
// increase touch poll from ~110 FPS to ~500 FPS (Sleep) or ~1000 FPS (Win10 high-res timer)
|
||||
// increase touch poll rate
|
||||
if (dwMilliseconds == 8) {
|
||||
dwMilliseconds = 1;
|
||||
dwMilliseconds = 1000 / games::rb::TOUCH_POLL_RATE;
|
||||
}
|
||||
|
||||
// most calls from rb are actually non-alertable
|
||||
if (!bAltertable) {
|
||||
if (!bAlertable) {
|
||||
static thread_local timeutils::PreciseSleepTimer timer;
|
||||
timer.sleep(dwMilliseconds);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// call original
|
||||
return SleepEx_orig(dwMilliseconds, bAltertable);
|
||||
return SleepEx_orig(dwMilliseconds, bAlertable);
|
||||
}
|
||||
|
||||
games::rb::RBGame::RBGame() : Game("Reflec Beat") {
|
||||
@@ -35,6 +40,10 @@ games::rb::RBGame::RBGame() : Game("Reflec Beat") {
|
||||
void games::rb::RBGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
if (1000 < games::rb::TOUCH_POLL_RATE) {
|
||||
log_fatal("rb", "-rbtouchsize is set too high; cannot exceed 1000");
|
||||
}
|
||||
|
||||
// init stuff
|
||||
devicehook_init();
|
||||
|
||||
@@ -47,7 +56,12 @@ void games::rb::RBGame::attach() {
|
||||
}
|
||||
|
||||
// hooks
|
||||
SleepEx_orig = detour::iat_try("SleepEx", SleepEx_hook, avs::game::DLL_INSTANCE);
|
||||
if (0 < TOUCH_POLL_RATE) {
|
||||
log_info("rb", "force increasing touch poll rate by hooking SleepEx ({}Hz)", TOUCH_POLL_RATE);
|
||||
SleepEx_orig = detour::iat_try("SleepEx", SleepEx_hook, avs::game::DLL_INSTANCE);
|
||||
} else {
|
||||
log_info("rb", "not changing touch poll rate (120Hz by default)");
|
||||
}
|
||||
}
|
||||
|
||||
void games::rb::RBGame::detach() {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
namespace games::rb {
|
||||
|
||||
extern uint16_t TOUCH_SCALING;
|
||||
extern uint8_t TOUCH_SIZE;
|
||||
extern uint16_t TOUCH_POLL_RATE;
|
||||
|
||||
class RBGame : public games::Game {
|
||||
public:
|
||||
|
||||
@@ -13,6 +13,7 @@ static std::string WINDOW_TITLE = "REFLEC BEAT";
|
||||
|
||||
namespace games::rb {
|
||||
uint16_t TOUCH_SCALING = 1000;
|
||||
uint8_t TOUCH_SIZE = 1;
|
||||
}
|
||||
|
||||
games::rb::ReflecBeatTouchDeviceHandle::ReflecBeatTouchDeviceHandle(bool log_fps) {
|
||||
@@ -160,16 +161,20 @@ int games::rb::ReflecBeatTouchDeviceHandle::read(LPVOID lpBuffer, DWORD nNumberO
|
||||
const auto point_x = (int) ((x - window_width / 2.0) * 48.0 / 54.0 + window_width / 2.0);
|
||||
const auto point_y = (int) (y - window_height / 76);
|
||||
|
||||
// insert 9 times with offset to double the precision
|
||||
// center
|
||||
grid_insert(data, point_x, point_y);
|
||||
grid_insert(data, point_x - offset_x, point_y);
|
||||
grid_insert(data, point_x - offset_x, point_y - offset_y);
|
||||
grid_insert(data, point_x - offset_x, point_y + offset_y);
|
||||
grid_insert(data, point_x + offset_x, point_y);
|
||||
grid_insert(data, point_x + offset_x, point_y + offset_y);
|
||||
grid_insert(data, point_x + offset_x, point_y - offset_y);
|
||||
grid_insert(data, point_x, point_y - offset_y);
|
||||
grid_insert(data, point_x, point_y + offset_y);
|
||||
|
||||
// surrounding points
|
||||
if (games::rb::TOUCH_SIZE == 3) {
|
||||
grid_insert(data, point_x - offset_x, point_y); // west
|
||||
grid_insert(data, point_x - offset_x, point_y - offset_y); // northwest
|
||||
grid_insert(data, point_x - offset_x, point_y + offset_y); // southwest
|
||||
grid_insert(data, point_x + offset_x, point_y); // east
|
||||
grid_insert(data, point_x + offset_x, point_y + offset_y); // southeast
|
||||
grid_insert(data, point_x + offset_x, point_y - offset_y); // northeast
|
||||
grid_insert(data, point_x, point_y - offset_y); // north
|
||||
grid_insert(data, point_x, point_y + offset_y); // south
|
||||
}
|
||||
}
|
||||
|
||||
// copy data to buffer
|
||||
|
||||
@@ -465,15 +465,21 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::RegisterSoftwareDevice(void *pIniti
|
||||
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterCount() {
|
||||
UINT result = pReal->GetAdapterCount();
|
||||
|
||||
if (!FAKE_SUBSCREEN_ADAPTER && games::popn::is_pikapika_model() && result == 1) {
|
||||
FAKE_SUBSCREEN_ADAPTER = true;
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"GetAdapterCount returned 1, popn needs 2 adapters - enabliing fake submonitor mode");
|
||||
}
|
||||
if (!FAKE_SUBSCREEN_ADAPTER) {
|
||||
if (games::popn::is_pikapika_model() && result == 1) {
|
||||
FAKE_SUBSCREEN_ADAPTER = true;
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"GetAdapterCount returned {}, popn needs 2 adapters - enabling fake submonitor mode", result);
|
||||
return 2;
|
||||
|
||||
if (FAKE_SUBSCREEN_ADAPTER) {
|
||||
return 2;
|
||||
} else if (games::gitadora::is_arena_model() && !GRAPHICS_WINDOWED && result < 4) {
|
||||
FAKE_SUBSCREEN_ADAPTER = true;
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"GetAdapterCount returned {}, gfdm needs 4 adapters - enabling fake submonitor mode", result);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -485,12 +491,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
|
||||
D3DADAPTER_IDENTIFIER9 *pIdentifier)
|
||||
{
|
||||
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1 && pIdentifier) {
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0 && pIdentifier) {
|
||||
*pIdentifier = {};
|
||||
strcpy(pIdentifier->DeviceName, "\\\\.\\DISPLAY_SPICE_FAKE");
|
||||
const std::string adapter_name = fmt::format("\\\\.\\DISPLAY_SPICE_FAKE_{}", Adapter);
|
||||
strcpy(pIdentifier->DeviceName, adapter_name.c_str());
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"GetAdapterIdentifier called for fake subscreen adapter 1: {}",
|
||||
"GetAdapterIdentifier called for fake subscreen adapter {}: {}",
|
||||
Adapter,
|
||||
pIdentifier->DeviceName);
|
||||
return S_OK;
|
||||
}
|
||||
@@ -499,8 +507,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
|
||||
}
|
||||
|
||||
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) {
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
|
||||
log_misc("graphics::d3d9", "GetAdapterModeCount called for fake subscreen adapter");
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0) {
|
||||
log_misc("graphics::d3d9", "GetAdapterModeCount called for fake subscreen adapter {}", Adapter);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -513,8 +521,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
UINT Mode,
|
||||
D3DDISPLAYMODE *pMode)
|
||||
{
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
|
||||
log_misc("graphics::d3d9", "EnumAdapterModes called for fake subscreen adapter");
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0) {
|
||||
log_misc("graphics::d3d9", "EnumAdapterModes called for fake subscreen adapter {}", Adapter);
|
||||
if (Mode == 0 && pMode) {
|
||||
pMode->Width = 1280;
|
||||
pMode->Height = 800;
|
||||
@@ -622,8 +630,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceFormat(
|
||||
D3DRESOURCETYPE RType,
|
||||
D3DFORMAT CheckFormat)
|
||||
{
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
|
||||
log_misc("graphics::d3d9", "CheckDeviceFormat called for fake subscreen adapter");
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0) {
|
||||
log_misc("graphics::d3d9", "CheckDeviceFormat called for fake subscreen adapter {}", Adapter);
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
@@ -1143,10 +1151,15 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
|
||||
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
|
||||
|
||||
// initialize sub screen if IIDX/SDVX requested a multi-head context
|
||||
if (avs::game::is_model({"LDJ", "KFC", "M39"}) &&
|
||||
// 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)) {
|
||||
graphics_d3d9_ldj_init_sub_screen(*ppReturnedDeviceInterface, &pPresentationParameters[1]);
|
||||
|
||||
UINT i = 1;
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
i = 2;
|
||||
}
|
||||
graphics_d3d9_ldj_init_sub_screen(*ppReturnedDeviceInterface, &pPresentationParameters[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1196,7 +1209,13 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
|
||||
log_info("graphics::d3d9", "created additional swap chain for windowed mode");
|
||||
}
|
||||
} else {
|
||||
hr = device->GetSwapChain(1, &SUB_SWAP_CHAIN);
|
||||
|
||||
int swapchain = 1;
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
swapchain = 2;
|
||||
}
|
||||
|
||||
hr = device->GetSwapChain(swapchain, &SUB_SWAP_CHAIN);
|
||||
if (FAILED(hr)) {
|
||||
log_warning("graphics::d3d9", "failed to acquire fullscreen sub swap chain, hr={}", FMT_HRESULT(hr));
|
||||
} else {
|
||||
@@ -1233,7 +1252,7 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
|
||||
// iidx/sdvx
|
||||
int swapchain = 1;
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
swapchain = 3;
|
||||
swapchain = 2;
|
||||
}
|
||||
|
||||
if (!ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE && SUB_SWAP_CHAIN == nullptr) {
|
||||
@@ -1452,8 +1471,9 @@ void graphics_d3d9_on_present(
|
||||
// for IIDX TDJ / SDVX UFC, handle subscreen
|
||||
const bool is_vm = games::sdvx::is_valkyrie_model();
|
||||
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
|
||||
const bool is_gfdm_arena =
|
||||
games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW;
|
||||
const bool is_gfdm_arena = games::gitadora::is_arena_model() &&
|
||||
(GRAPHICS_FORCE_SINGLE_ADAPTER || GRAPHICS_PREVENT_SECONDARY_WINDOW);
|
||||
|
||||
const bool is_pika = games::popn::is_pikapika_model();
|
||||
if (is_vm || is_tdj || is_gfdm_arena || is_pika) {
|
||||
graphics_d3d9_ldj_on_present(wrapped_device);
|
||||
|
||||
@@ -129,13 +129,17 @@ ULONG STDMETHODCALLTYPE WrappedIDirect3DDevice9::Release() {
|
||||
this->main_swapchain->Release();
|
||||
this->main_swapchain = nullptr;
|
||||
}
|
||||
if (this->sub_swapchain) {
|
||||
this->sub_swapchain->Release();
|
||||
this->sub_swapchain = nullptr;
|
||||
for (auto &sc : this->sub_swapchain) {
|
||||
if (sc) {
|
||||
sc->Release();
|
||||
sc = nullptr;
|
||||
}
|
||||
}
|
||||
if (this->fake_sub_swapchain) {
|
||||
this->fake_sub_swapchain->Release();
|
||||
this->fake_sub_swapchain = nullptr;
|
||||
for (auto &sc : this->fake_sub_swapchain) {
|
||||
if (sc) {
|
||||
sc->Release();
|
||||
sc = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (overlay::ENABLED) {
|
||||
@@ -250,27 +254,47 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
|
||||
WRAP_VERBOSE;
|
||||
|
||||
HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain);
|
||||
|
||||
int index = 0;
|
||||
bool create_swap_chain = false;
|
||||
if (avs::game::is_model({"LDJ", "KFC", "M39"})) {
|
||||
create_swap_chain = true;
|
||||
|
||||
} else if (games::gitadora::is_arena_model() &&
|
||||
games::gitadora::ARENA_SINGLE_WINDOW &&
|
||||
pPresentationParameters->BackBufferWidth == 800) {
|
||||
create_swap_chain = true;
|
||||
(GRAPHICS_FORCE_SINGLE_ADAPTER || GRAPHICS_PREVENT_SECONDARY_WINDOW)) {
|
||||
if (pPresentationParameters->BackBufferWidth == 800) {
|
||||
// SMALL (subscreen)
|
||||
create_swap_chain = true;
|
||||
index = 0;
|
||||
} else if (pPresentationParameters->BackBufferWidth == 1080) {
|
||||
// LEFT/RIGHT
|
||||
create_swap_chain = true;
|
||||
index = 1;
|
||||
if (sub_swapchain[index] || fake_sub_swapchain[index]) {
|
||||
index = 2;
|
||||
}
|
||||
} else {
|
||||
log_warning("graphics::d3d9", "unknown swap chain detected in CreateAdditionalSwapChain");
|
||||
}
|
||||
}
|
||||
|
||||
if (create_swap_chain) {
|
||||
if (SUCCEEDED(hr) && !sub_swapchain) {
|
||||
sub_swapchain = new WrappedIDirect3DSwapChain9(this, *ppSwapChain);
|
||||
sub_swapchain->should_run_hooks = false;
|
||||
} else if (FAILED(hr) && !fake_sub_swapchain) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"CreateAdditionalSwapChain called for swap chain {}, creating swap chain",
|
||||
index);
|
||||
|
||||
if (SUCCEEDED(hr) && !sub_swapchain[index]) {
|
||||
sub_swapchain[index] = new WrappedIDirect3DSwapChain9(this, *ppSwapChain);
|
||||
sub_swapchain[index]->should_run_hooks = false;
|
||||
} else if (FAILED(hr) && !fake_sub_swapchain[index]) {
|
||||
log_warning("graphics::d3d9",
|
||||
"failed to create sub swap chain, hr={}, using fake swap chain",
|
||||
FMT_HRESULT(hr));
|
||||
|
||||
fake_sub_swapchain = new FakeIDirect3DSwapChain9(this, pPresentationParameters, false);
|
||||
fake_sub_swapchain->AddRef();
|
||||
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(fake_sub_swapchain);
|
||||
fake_sub_swapchain[index] = new FakeIDirect3DSwapChain9(this, pPresentationParameters, false);
|
||||
fake_sub_swapchain[index]->AddRef();
|
||||
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(fake_sub_swapchain[index]);
|
||||
|
||||
return D3D_OK;
|
||||
}
|
||||
@@ -308,22 +332,20 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
|
||||
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC", "M39"})) {
|
||||
swap = true;
|
||||
}
|
||||
if (games::gitadora::is_arena_model() &&
|
||||
games::gitadora::ARENA_SINGLE_WINDOW &&
|
||||
iSwapChain == 3) {
|
||||
if (games::gitadora::is_arena_model() && iSwapChain == 2) {
|
||||
swap = true;
|
||||
}
|
||||
|
||||
if (swap) {
|
||||
if (sub_swapchain) {
|
||||
sub_swapchain->AddRef();
|
||||
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(sub_swapchain);
|
||||
if (sub_swapchain[0]) {
|
||||
sub_swapchain[0]->AddRef();
|
||||
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(sub_swapchain[0]);
|
||||
|
||||
graphics_screens_register(iSwapChain);
|
||||
return D3D_OK;
|
||||
} else if (fake_sub_swapchain) {
|
||||
fake_sub_swapchain->AddRef();
|
||||
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(fake_sub_swapchain);
|
||||
} else if (fake_sub_swapchain[0]) {
|
||||
fake_sub_swapchain[0]->AddRef();
|
||||
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(fake_sub_swapchain[0]);
|
||||
|
||||
graphics_screens_register(iSwapChain);
|
||||
return D3D_OK;
|
||||
@@ -344,7 +366,7 @@ UINT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetNumberOfSwapChains() {
|
||||
|
||||
UINT n = pReal->GetNumberOfSwapChains();
|
||||
|
||||
if (sub_swapchain && avs::game::is_model({"LDJ", "KFC", "M39"})) {
|
||||
if (sub_swapchain[0] && avs::game::is_model({"LDJ", "KFC", "M39"})) {
|
||||
n += 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -204,7 +204,8 @@ struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex {
|
||||
std::atomic_ulong refs = 1;
|
||||
|
||||
WrappedIDirect3DSwapChain9 *main_swapchain = nullptr;
|
||||
WrappedIDirect3DSwapChain9 *sub_swapchain = nullptr;
|
||||
FakeIDirect3DSwapChain9 *fake_sub_swapchain = nullptr;
|
||||
WrappedIDirect3DSwapChain9 *sub_swapchain[3] = { nullptr, nullptr, nullptr };
|
||||
FakeIDirect3DSwapChain9 *fake_sub_swapchain[3] = { nullptr, nullptr, nullptr };
|
||||
|
||||
IDirect3DVertexShader9 *vertex_shader = nullptr;
|
||||
};
|
||||
|
||||
@@ -421,7 +421,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
}
|
||||
|
||||
// only hook touch window if multiple windows are allowed
|
||||
if (is_gfdm_sub_window && !games::gitadora::ARENA_SINGLE_WINDOW) {
|
||||
if (is_gfdm_sub_window && GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
||||
GFDM_SUBSCREEN_WINDOW = result;
|
||||
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
|
||||
}
|
||||
@@ -702,7 +702,7 @@ static BOOL WINAPI SetWindowPos_hook(HWND hWnd, HWND hWndInsertAfter,
|
||||
|
||||
static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
|
||||
if (games::gitadora::is_arena_model() &&
|
||||
games::gitadora::ARENA_SINGLE_WINDOW &&
|
||||
GRAPHICS_PREVENT_SECONDARY_WINDOW &&
|
||||
hWnd != GRAPHICS_HOOKED_WINDOW) {
|
||||
log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd));
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <iptypes.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <icmpapi.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct IcmpHdr {
|
||||
uint8_t type;
|
||||
uint8_t code;
|
||||
uint16_t checksum_be;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
decltype(IcmpSendEcho) *IcmpSendEcho_orig = nullptr;
|
||||
|
||||
DWORD WINAPI IcmpSendEcho_hook(
|
||||
HANDLE IcmpHandle,
|
||||
IPAddr DestinationAddress,
|
||||
LPVOID RequestData,
|
||||
WORD RequestSize,
|
||||
PIP_OPTION_INFORMATION RequestOptions,
|
||||
LPVOID ReplyBuffer,
|
||||
DWORD ReplySize,
|
||||
DWORD Timeout) {
|
||||
if (!IcmpSendEcho_orig) {
|
||||
SetLastError(ERROR_INVALID_FUNCTION);
|
||||
return 0;
|
||||
}
|
||||
DWORD n = IcmpSendEcho_orig(
|
||||
IcmpHandle,
|
||||
DestinationAddress,
|
||||
RequestData,
|
||||
RequestSize,
|
||||
RequestOptions,
|
||||
ReplyBuffer,
|
||||
ReplySize,
|
||||
Timeout);
|
||||
if (n != 0) {
|
||||
return n;
|
||||
}
|
||||
if (ReplyBuffer == nullptr || ReplySize < sizeof(ICMP_ECHO_REPLY) || RequestData == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
if (RequestSize < sizeof(IcmpHdr)) {
|
||||
return 0;
|
||||
}
|
||||
const auto *req = reinterpret_cast<const uint8_t *>(RequestData);
|
||||
const auto *ih = reinterpret_cast<const IcmpHdr *>(req);
|
||||
if (ih->type != 8 || ih->code != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(ReplyBuffer, 0, ReplySize);
|
||||
auto *reply = reinterpret_cast<ICMP_ECHO_REPLY *>(ReplyBuffer);
|
||||
const size_t data_off = offsetof(ICMP_ECHO_REPLY, Data);
|
||||
const size_t avail = ReplySize > data_off ? ReplySize - data_off : 0;
|
||||
const size_t copy_len = (std::min)(static_cast<size_t>(RequestSize), avail);
|
||||
reply->Address = DestinationAddress;
|
||||
reply->Status = IP_SUCCESS;
|
||||
reply->RoundTripTime = 1;
|
||||
reply->DataSize = static_cast<USHORT>(copy_len);
|
||||
if (copy_len > 0) {
|
||||
memcpy(reinterpret_cast<uint8_t *>(ReplyBuffer) + data_off, RequestData, copy_len);
|
||||
}
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void icmphook_iphlpapi_install() {
|
||||
static bool done = false;
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
done = true;
|
||||
if (!detour::trampoline_try(
|
||||
"iphlpapi.dll",
|
||||
"IcmpSendEcho",
|
||||
(void *) IcmpSendEcho_hook,
|
||||
(void **) &IcmpSendEcho_orig)) {
|
||||
log_warning("network", "ICMP emulation: IcmpSendEcho hook was not installed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,692 @@
|
||||
#include "icmphook_net.h"
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
#include <iphlpapi.h>
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <unordered_map>
|
||||
|
||||
void icmphook_iphlpapi_install();
|
||||
|
||||
#ifndef SPICE64
|
||||
using socket_uint = unsigned int;
|
||||
#else
|
||||
using socket_uint = unsigned long long;
|
||||
#endif
|
||||
|
||||
namespace icmphook_internal {
|
||||
|
||||
constexpr uint32_t k_loopback_host = 0x7f000001u;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct IcmpHeaderView {
|
||||
uint8_t type;
|
||||
uint8_t code;
|
||||
uint16_t checksum_be;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
bool icmp_packet_valid(const uint8_t *icmp, size_t len) {
|
||||
if (len < sizeof(IcmpHeaderView)) {
|
||||
return false;
|
||||
}
|
||||
unsigned sum = 0;
|
||||
for (size_t i = 0; i + 1 < len; i += 2) {
|
||||
sum += (static_cast<unsigned>(icmp[i]) << 8) | icmp[i + 1];
|
||||
sum = (sum >> 16) + (sum & 0xFFFF);
|
||||
}
|
||||
if (len & 1) {
|
||||
sum += static_cast<unsigned>(icmp[len - 1]) << 8;
|
||||
sum = (sum >> 16) + (sum & 0xFFFF);
|
||||
}
|
||||
sum = (sum >> 16) + (sum & 0xFFFF);
|
||||
return sum == 0xFFFFu || sum == 0;
|
||||
}
|
||||
|
||||
bool icmp_build_echo_reply(std::vector<uint8_t> &out, const uint8_t *req, size_t req_len) {
|
||||
if (req_len < sizeof(IcmpHeaderView)) {
|
||||
return false;
|
||||
}
|
||||
out.resize(req_len);
|
||||
memcpy(out.data(), req, req_len);
|
||||
auto *hdr = reinterpret_cast<IcmpHeaderView *>(out.data());
|
||||
hdr->type = 0;
|
||||
hdr->code = 0;
|
||||
hdr->checksum_be = 0;
|
||||
unsigned sum = 0;
|
||||
for (size_t i = 0; i + 1 < out.size(); i += 2) {
|
||||
sum += (static_cast<unsigned>(out[i]) << 8) | out[i + 1];
|
||||
sum = (sum >> 16) + (sum & 0xFFFF);
|
||||
}
|
||||
if (out.size() & 1) {
|
||||
sum += static_cast<unsigned>(out[out.size() - 1]) << 8;
|
||||
sum = (sum >> 16) + (sum & 0xFFFF);
|
||||
}
|
||||
sum = (sum >> 16) + (sum & 0xFFFF);
|
||||
uint16_t csum = static_cast<uint16_t>(~sum);
|
||||
hdr->checksum_be = htons(csum);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool icmp_fix_pointer(
|
||||
const uint8_t *base,
|
||||
size_t total_len,
|
||||
const uint8_t **icmp_start,
|
||||
size_t *icmp_len) {
|
||||
if (total_len >= 20 && (base[0] >> 4) == 4) {
|
||||
size_t ihl = (base[0] & 0x0Fu) * 4;
|
||||
if (ihl < 20 || ihl > total_len || base[9] != IPPROTO_ICMP) {
|
||||
return false;
|
||||
}
|
||||
*icmp_start = base + ihl;
|
||||
*icmp_len = total_len - ihl;
|
||||
return true;
|
||||
}
|
||||
*icmp_start = base;
|
||||
*icmp_len = total_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ipv4_encode_datagram(
|
||||
std::vector<uint8_t> &out,
|
||||
uint32_t src_host_order,
|
||||
uint32_t dst_host_order,
|
||||
const uint8_t *payload,
|
||||
size_t payload_len) {
|
||||
const size_t hdr_len = 20;
|
||||
if (payload_len > 0xFFFFu - hdr_len) {
|
||||
return false;
|
||||
}
|
||||
const auto total_len = static_cast<uint16_t>(hdr_len + payload_len);
|
||||
out.resize(hdr_len + payload_len);
|
||||
uint8_t *p = out.data();
|
||||
memset(p, 0, hdr_len);
|
||||
p[0] = 0x45;
|
||||
p[1] = 0;
|
||||
p[2] = static_cast<uint8_t>(total_len >> 8);
|
||||
p[3] = static_cast<uint8_t>(total_len & 0xFF);
|
||||
p[6] = 0x40;
|
||||
p[8] = 1;
|
||||
p[9] = IPPROTO_ICMP;
|
||||
uint32_t src_wire = htonl(src_host_order);
|
||||
uint32_t dst_wire = htonl(dst_host_order);
|
||||
memcpy(p + 12, &src_wire, 4);
|
||||
memcpy(p + 16, &dst_wire, 4);
|
||||
unsigned sum = 0;
|
||||
for (size_t i = 0; i < hdr_len; i += 2) {
|
||||
sum += (static_cast<unsigned>(p[i]) << 8) | p[i + 1];
|
||||
while (sum >> 16) {
|
||||
sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
}
|
||||
}
|
||||
uint16_t hdr_csum = static_cast<uint16_t>(~sum);
|
||||
p[10] = static_cast<uint8_t>(hdr_csum >> 8);
|
||||
p[11] = static_cast<uint8_t>(hdr_csum & 0xFF);
|
||||
memcpy(p + hdr_len, payload, payload_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cached adapter list. Populated once per process: adapter changes after the
|
||||
// first ICMP send are intentionally not picked up, in exchange for avoiding
|
||||
// repeated IP Helper calls on every emulated send.
|
||||
std::once_flag g_adapters_cache_init;
|
||||
std::vector<uint8_t> g_adapters_cache;
|
||||
|
||||
void fill_adapters_cache() {
|
||||
std::call_once(g_adapters_cache_init, []() {
|
||||
ULONG buf_len = 0;
|
||||
DWORD r = GetAdaptersInfo(nullptr, &buf_len);
|
||||
if (r != ERROR_BUFFER_OVERFLOW && r != ERROR_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
if (buf_len == 0) {
|
||||
return;
|
||||
}
|
||||
g_adapters_cache.resize(buf_len);
|
||||
auto *info = reinterpret_cast<PIP_ADAPTER_INFO>(g_adapters_cache.data());
|
||||
if (GetAdaptersInfo(info, &buf_len) != ERROR_SUCCESS) {
|
||||
g_adapters_cache.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t local_ipv4_for_peer(
|
||||
uint32_t peer_host_order,
|
||||
uint32_t bind_pref_host_order,
|
||||
bool has_bind) {
|
||||
if (peer_host_order == k_loopback_host) {
|
||||
return k_loopback_host;
|
||||
}
|
||||
if (has_bind && bind_pref_host_order != 0) {
|
||||
return bind_pref_host_order;
|
||||
}
|
||||
fill_adapters_cache();
|
||||
if (g_adapters_cache.empty()) {
|
||||
return peer_host_order;
|
||||
}
|
||||
auto *info = reinterpret_cast<PIP_ADAPTER_INFO>(g_adapters_cache.data());
|
||||
for (PIP_ADAPTER_INFO a = info; a != nullptr; a = a->Next) {
|
||||
uint32_t ip = ntohl(inet_addr(a->IpAddressList.IpAddress.String));
|
||||
uint32_t mask = ntohl(inet_addr(a->IpAddressList.IpMask.String));
|
||||
if (mask == 0) {
|
||||
continue;
|
||||
}
|
||||
if ((peer_host_order & mask) == (ip & mask)) {
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
return peer_host_order;
|
||||
}
|
||||
|
||||
struct EmuSock {
|
||||
std::deque<std::vector<uint8_t>> queue;
|
||||
std::condition_variable_any cv;
|
||||
DWORD rx_timeout_ms = INFINITE;
|
||||
bool nonblocking = false;
|
||||
bool has_bind = false;
|
||||
uint32_t bind_ip_host = 0;
|
||||
};
|
||||
|
||||
std::recursive_mutex g_mu;
|
||||
std::unordered_map<SOCKET, std::unique_ptr<EmuSock>> g_socks;
|
||||
std::atomic<uint32_t> g_seq{1};
|
||||
|
||||
// Fast-path flag so the bind() hook in networkhook.cpp can short-circuit
|
||||
// without taking g_mu when the ICMP feature was never enabled.
|
||||
std::atomic<bool> g_installed{false};
|
||||
|
||||
decltype(socket) *socket_orig = nullptr;
|
||||
decltype(WSASocketW) *WSASocketW_orig = nullptr;
|
||||
decltype(WSASocketA) *WSASocketA_orig = nullptr;
|
||||
decltype(closesocket) *closesocket_orig = nullptr;
|
||||
decltype(bind) *bind_trampoline_orig = nullptr;
|
||||
decltype(sendto) *sendto_orig = nullptr;
|
||||
decltype(recvfrom) *recvfrom_orig = nullptr;
|
||||
decltype(WSASendTo) *WSASendTo_orig = nullptr;
|
||||
decltype(WSARecvFrom) *WSARecvFrom_orig = nullptr;
|
||||
decltype(ioctlsocket) *ioctlsocket_orig = nullptr;
|
||||
decltype(setsockopt) *setsockopt_orig = nullptr;
|
||||
|
||||
EmuSock *lookup(SOCKET s) {
|
||||
std::lock_guard<std::recursive_mutex> lock(g_mu);
|
||||
auto it = g_socks.find(s);
|
||||
if (it == g_socks.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
SOCKET alloc_icmp_socket() {
|
||||
std::lock_guard<std::recursive_mutex> lock(g_mu);
|
||||
for (;;) {
|
||||
uint32_t x = g_seq.fetch_add(1, std::memory_order_relaxed);
|
||||
SOCKET s = (SOCKET)(socket_uint)(0xE0000000u | (x & 0x0FFFFFFFu));
|
||||
if (s == INVALID_SOCKET) {
|
||||
continue;
|
||||
}
|
||||
if (g_socks.find(s) == g_socks.end()) {
|
||||
g_socks[s] = std::make_unique<EmuSock>();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void release_icmp_socket(SOCKET s) {
|
||||
std::lock_guard<std::recursive_mutex> lock(g_mu);
|
||||
auto it = g_socks.find(s);
|
||||
if (it != g_socks.end()) {
|
||||
it->second->cv.notify_all();
|
||||
g_socks.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
bool process_icmp_send(SOCKET s, const uint8_t *buf, int len, const sockaddr *to, int tolen) {
|
||||
if (len <= 0 || tolen < (int) sizeof(sockaddr_in) || !buf || !to) {
|
||||
return true;
|
||||
}
|
||||
auto *sin = reinterpret_cast<const sockaddr_in *>(to);
|
||||
if (sin->sin_family != AF_INET) {
|
||||
return true;
|
||||
}
|
||||
uint32_t peer_host = ntohl(sin->sin_addr.s_addr);
|
||||
|
||||
const uint8_t *icmp_ptr = nullptr;
|
||||
size_t icmp_len = 0;
|
||||
const auto *raw = reinterpret_cast<const uint8_t *>(buf);
|
||||
if (!icmp_fix_pointer(raw, static_cast<size_t>(len), &icmp_ptr, &icmp_len)) {
|
||||
return true;
|
||||
}
|
||||
if (!icmp_packet_valid(icmp_ptr, icmp_len)) {
|
||||
return true;
|
||||
}
|
||||
if (icmp_len < sizeof(IcmpHeaderView)) {
|
||||
return true;
|
||||
}
|
||||
const auto *ih = reinterpret_cast<const IcmpHeaderView *>(icmp_ptr);
|
||||
if (ih->type != 8 || ih->code != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> icmp_reply;
|
||||
if (!icmp_build_echo_reply(icmp_reply, icmp_ptr, icmp_len)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> packet;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(g_mu);
|
||||
auto it = g_socks.find(s);
|
||||
if (it == g_socks.end()) {
|
||||
return true;
|
||||
}
|
||||
EmuSock *es = it->second.get();
|
||||
uint32_t local_host = local_ipv4_for_peer(peer_host, es->bind_ip_host, es->has_bind);
|
||||
if (!ipv4_encode_datagram(
|
||||
packet, peer_host, local_host, icmp_reply.data(), icmp_reply.size())) {
|
||||
return true;
|
||||
}
|
||||
es->queue.push_back(std::move(packet));
|
||||
es->cv.notify_all();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SOCKET WINAPI socket_hook(int af, int type, int protocol) {
|
||||
if (af == AF_INET && type == SOCK_RAW && protocol == IPPROTO_ICMP) {
|
||||
SOCKET s = alloc_icmp_socket();
|
||||
log_info("network", "ICMP emulation: allocated raw socket {}", (socket_uint) s);
|
||||
return s;
|
||||
}
|
||||
return socket_orig(af, type, protocol);
|
||||
}
|
||||
|
||||
SOCKET WINAPI WSASocketW_hook(
|
||||
int af,
|
||||
int type,
|
||||
int protocol,
|
||||
LPWSAPROTOCOL_INFOW lpProtocolInfo,
|
||||
GROUP g,
|
||||
DWORD dwFlags) {
|
||||
const bool is_raw_icmp =
|
||||
af == AF_INET && type == SOCK_RAW && protocol == IPPROTO_ICMP;
|
||||
if (lpProtocolInfo == nullptr && is_raw_icmp) {
|
||||
SOCKET s = alloc_icmp_socket();
|
||||
log_info("network", "ICMP emulation: allocated raw WSASocketW {}", (socket_uint) s);
|
||||
return s;
|
||||
}
|
||||
return WSASocketW_orig(af, type, protocol, lpProtocolInfo, g, dwFlags);
|
||||
}
|
||||
|
||||
SOCKET WINAPI WSASocketA_hook(
|
||||
int af,
|
||||
int type,
|
||||
int protocol,
|
||||
LPWSAPROTOCOL_INFOA lpProtocolInfo,
|
||||
GROUP g,
|
||||
DWORD dwFlags) {
|
||||
const bool is_raw_icmp =
|
||||
af == AF_INET && type == SOCK_RAW && protocol == IPPROTO_ICMP;
|
||||
if (lpProtocolInfo == nullptr && is_raw_icmp) {
|
||||
SOCKET s = alloc_icmp_socket();
|
||||
log_info("network", "ICMP emulation: allocated raw WSASocketA {}", (socket_uint) s);
|
||||
return s;
|
||||
}
|
||||
return WSASocketA_orig(af, type, protocol, lpProtocolInfo, g, dwFlags);
|
||||
}
|
||||
|
||||
int WINAPI closesocket_hook(SOCKET s) {
|
||||
if (lookup(s)) {
|
||||
release_icmp_socket(s);
|
||||
return 0;
|
||||
}
|
||||
return closesocket_orig(s);
|
||||
}
|
||||
|
||||
int WINAPI bind_hook_ws2(SOCKET s, const sockaddr *name, int namelen) {
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(g_mu);
|
||||
auto it = g_socks.find(s);
|
||||
if (it != g_socks.end() && name && namelen >= (int) sizeof(sockaddr_in)) {
|
||||
auto *in = reinterpret_cast<const sockaddr_in *>(name);
|
||||
if (in->sin_family != AF_INET) {
|
||||
WSASetLastError(WSAEAFNOSUPPORT);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
it->second->has_bind = true;
|
||||
it->second->bind_ip_host = ntohl(in->sin_addr.s_addr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return bind_trampoline_orig(s, name, namelen);
|
||||
}
|
||||
|
||||
int WINAPI sendto_hook(
|
||||
SOCKET s, const char *buf, int len, int flags, const sockaddr *to, int tolen) {
|
||||
EmuSock *es = lookup(s);
|
||||
if (!es) {
|
||||
return sendto_orig(s, buf, len, flags, to, tolen);
|
||||
}
|
||||
if (!buf || !to) {
|
||||
WSASetLastError(WSAEFAULT);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
process_icmp_send(s, reinterpret_cast<const uint8_t *>(buf), len, to, tolen);
|
||||
return len;
|
||||
}
|
||||
|
||||
int WINAPI WSASendTo_hook(
|
||||
SOCKET s,
|
||||
LPWSABUF lpBuffers,
|
||||
DWORD dwBufferCount,
|
||||
LPDWORD lpNumberOfBytesSent,
|
||||
DWORD dwFlags,
|
||||
const sockaddr *lpTo,
|
||||
int iTolen,
|
||||
LPWSAOVERLAPPED lpOverlapped,
|
||||
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) {
|
||||
EmuSock *es = lookup(s);
|
||||
if (!es) {
|
||||
return WSASendTo_orig(
|
||||
s,
|
||||
lpBuffers,
|
||||
dwBufferCount,
|
||||
lpNumberOfBytesSent,
|
||||
dwFlags,
|
||||
lpTo,
|
||||
iTolen,
|
||||
lpOverlapped,
|
||||
lpCompletionRoutine);
|
||||
}
|
||||
if (lpOverlapped != nullptr) {
|
||||
WSASetLastError(WSAEOPNOTSUPP);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
if (dwBufferCount != 1 || !lpBuffers || !lpNumberOfBytesSent) {
|
||||
WSASetLastError(WSAEINVAL);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
int len = (int) lpBuffers[0].len;
|
||||
process_icmp_send(
|
||||
s,
|
||||
reinterpret_cast<const uint8_t *>(lpBuffers[0].buf),
|
||||
len,
|
||||
lpTo,
|
||||
iTolen);
|
||||
*lpNumberOfBytesSent = static_cast<DWORD>(len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Dequeue one datagram for an emulated ICMP socket into the caller-provided
|
||||
// buffer. If out_full_dgram_bytes is non-null, it receives the full packet
|
||||
// size (before truncation to len) so callers can signal WSAEMSGSIZE.
|
||||
int emu_recv_dequeue(
|
||||
SOCKET s, char *buf, int len, sockaddr *from, int *fromlen,
|
||||
size_t *out_full_dgram_bytes) {
|
||||
if (!buf || len <= 0) {
|
||||
WSASetLastError(WSAEINVAL);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> pkt;
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(g_mu);
|
||||
auto it = g_socks.find(s);
|
||||
if (it == g_socks.end()) {
|
||||
WSASetLastError(WSAENOTSOCK);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
|
||||
auto wait_pred = [&] {
|
||||
auto j = g_socks.find(s);
|
||||
return j == g_socks.end() || !j->second->queue.empty();
|
||||
};
|
||||
|
||||
EmuSock *es = it->second.get();
|
||||
if (es->nonblocking) {
|
||||
if (es->queue.empty()) {
|
||||
WSASetLastError(WSAEWOULDBLOCK);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
} else if (es->rx_timeout_ms == INFINITE || es->rx_timeout_ms == 0) {
|
||||
it->second->cv.wait(lock, wait_pred);
|
||||
} else {
|
||||
const auto timeout = std::chrono::milliseconds(es->rx_timeout_ms);
|
||||
if (!it->second->cv.wait_for(lock, timeout, wait_pred)) {
|
||||
WSASetLastError(WSAETIMEDOUT);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
it = g_socks.find(s);
|
||||
if (it == g_socks.end()) {
|
||||
WSASetLastError(WSAENOTSOCK);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
es = it->second.get();
|
||||
if (es->queue.empty()) {
|
||||
WSASetLastError(WSAENOTSOCK);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
|
||||
pkt = std::move(es->queue.front());
|
||||
es->queue.pop_front();
|
||||
}
|
||||
|
||||
if (out_full_dgram_bytes) {
|
||||
*out_full_dgram_bytes = pkt.size();
|
||||
}
|
||||
const int take = (len < (int) pkt.size()) ? len : (int) pkt.size();
|
||||
memcpy(buf, pkt.data(), static_cast<size_t>(take));
|
||||
if (from && fromlen && *fromlen >= (int) sizeof(sockaddr_in)) {
|
||||
auto *out = reinterpret_cast<sockaddr_in *>(from);
|
||||
memset(out, 0, sizeof(*out));
|
||||
out->sin_family = AF_INET;
|
||||
if (pkt.size() >= 20) {
|
||||
memcpy(&out->sin_addr, pkt.data() + 12, 4);
|
||||
} else {
|
||||
out->sin_addr.s_addr = htonl(k_loopback_host);
|
||||
}
|
||||
*fromlen = sizeof(sockaddr_in);
|
||||
}
|
||||
return take;
|
||||
}
|
||||
|
||||
int WINAPI recvfrom_hook(
|
||||
SOCKET s, char *buf, int len, int flags, sockaddr *from, int *fromlen) {
|
||||
if (!lookup(s)) {
|
||||
return recvfrom_orig(s, buf, len, flags, from, fromlen);
|
||||
}
|
||||
return emu_recv_dequeue(s, buf, len, from, fromlen, nullptr);
|
||||
}
|
||||
|
||||
int WINAPI WSARecvFrom_hook(
|
||||
SOCKET s,
|
||||
LPWSABUF lpBuffers,
|
||||
DWORD dwBufferCount,
|
||||
LPDWORD lpNumberOfBytesRecvd,
|
||||
LPDWORD lpFlags,
|
||||
sockaddr *lpFrom,
|
||||
LPINT lpFromlen,
|
||||
LPWSAOVERLAPPED lpOverlapped,
|
||||
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) {
|
||||
if (!lookup(s)) {
|
||||
return WSARecvFrom_orig(
|
||||
s,
|
||||
lpBuffers,
|
||||
dwBufferCount,
|
||||
lpNumberOfBytesRecvd,
|
||||
lpFlags,
|
||||
lpFrom,
|
||||
lpFromlen,
|
||||
lpOverlapped,
|
||||
lpCompletionRoutine);
|
||||
}
|
||||
if (lpOverlapped != nullptr) {
|
||||
WSASetLastError(WSAEOPNOTSUPP);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
if (dwBufferCount != 1 || !lpBuffers || !lpNumberOfBytesRecvd) {
|
||||
WSASetLastError(WSAEINVAL);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
sockaddr_in from_tmp{};
|
||||
int fromlen_tmp = sizeof(from_tmp);
|
||||
size_t full_bytes = 0;
|
||||
int r = emu_recv_dequeue(
|
||||
s,
|
||||
lpBuffers[0].buf,
|
||||
(int) lpBuffers[0].len,
|
||||
reinterpret_cast<sockaddr *>(&from_tmp),
|
||||
&fromlen_tmp,
|
||||
&full_bytes);
|
||||
if (r == SOCKET_ERROR) {
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
if (full_bytes > lpBuffers[0].len) {
|
||||
WSASetLastError(WSAEMSGSIZE);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
*lpNumberOfBytesRecvd = static_cast<DWORD>(r);
|
||||
if (lpFlags) {
|
||||
*lpFlags = 0;
|
||||
}
|
||||
if (lpFrom && lpFromlen && *lpFromlen >= (int) sizeof(sockaddr_in)) {
|
||||
*reinterpret_cast<sockaddr_in *>(lpFrom) = from_tmp;
|
||||
*lpFromlen = sizeof(sockaddr_in);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WINAPI ioctlsocket_hook(SOCKET s, long cmd, u_long *argp) {
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(g_mu);
|
||||
auto it = g_socks.find(s);
|
||||
if (it != g_socks.end()) {
|
||||
EmuSock *es = it->second.get();
|
||||
if (argp && static_cast<u_long>(cmd) == FIONBIO) {
|
||||
es->nonblocking = (*argp != 0);
|
||||
return 0;
|
||||
}
|
||||
WSASetLastError(WSAEOPNOTSUPP);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
}
|
||||
return ioctlsocket_orig(s, cmd, argp);
|
||||
}
|
||||
|
||||
int WINAPI setsockopt_hook(
|
||||
SOCKET s, int level, int optname, const char *optval, int optlen) {
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(g_mu);
|
||||
auto it = g_socks.find(s);
|
||||
if (it != g_socks.end()) {
|
||||
const bool deref_optval = level == SOL_SOCKET && optlen >= (int) sizeof(DWORD);
|
||||
if (deref_optval && optval) {
|
||||
EmuSock *es = it->second.get();
|
||||
auto v = reinterpret_cast<const DWORD *>(optval);
|
||||
if (optname == SO_RCVTIMEO) {
|
||||
DWORD ms = *v;
|
||||
es->rx_timeout_ms = (ms == 0) ? INFINITE : ms;
|
||||
return 0;
|
||||
}
|
||||
if (optname == SO_SNDTIMEO) {
|
||||
return 0;
|
||||
}
|
||||
WSASetLastError(WSAEOPNOTSUPP);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
if (!deref_optval) {
|
||||
WSASetLastError(WSAEOPNOTSUPP);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return setsockopt_orig(s, level, optname, optval, optlen);
|
||||
}
|
||||
|
||||
void install_icmphook_hooks() {
|
||||
static bool done = false;
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
done = true;
|
||||
|
||||
bool ok = true;
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "socket",
|
||||
(void *) socket_hook, (void **) &socket_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "WSASocketW",
|
||||
(void *) WSASocketW_hook, (void **) &WSASocketW_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "WSASocketA",
|
||||
(void *) WSASocketA_hook, (void **) &WSASocketA_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "closesocket",
|
||||
(void *) closesocket_hook, (void **) &closesocket_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "bind",
|
||||
(void *) bind_hook_ws2, (void **) &bind_trampoline_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "sendto",
|
||||
(void *) sendto_hook, (void **) &sendto_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "recvfrom",
|
||||
(void *) recvfrom_hook, (void **) &recvfrom_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "WSASendTo",
|
||||
(void *) WSASendTo_hook, (void **) &WSASendTo_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "WSARecvFrom",
|
||||
(void *) WSARecvFrom_hook, (void **) &WSARecvFrom_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "ioctlsocket",
|
||||
(void *) ioctlsocket_hook, (void **) &ioctlsocket_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "setsockopt",
|
||||
(void *) setsockopt_hook, (void **) &setsockopt_orig);
|
||||
|
||||
if (!ok) {
|
||||
log_warning(
|
||||
"network",
|
||||
"ICMP emulation: one or more ws2_32 hooks failed to install");
|
||||
} else {
|
||||
log_info("network", "ICMP emulation hooks installed (raw ICMP sockets)");
|
||||
}
|
||||
|
||||
g_installed.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
} // namespace icmphook_internal
|
||||
|
||||
bool icmphook_is_emulated_socket(SOCKET s) {
|
||||
if (!icmphook_internal::g_installed.load(std::memory_order_acquire)) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::recursive_mutex> lock(icmphook_internal::g_mu);
|
||||
return icmphook_internal::g_socks.find(s) != icmphook_internal::g_socks.end();
|
||||
}
|
||||
|
||||
bool icmphook_try_bind(SOCKET s, const struct sockaddr *name, int namelen, int *out_result) {
|
||||
if (!icmphook_internal::g_installed.load(std::memory_order_acquire)) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::recursive_mutex> lock(icmphook_internal::g_mu);
|
||||
if (icmphook_internal::g_socks.find(s) == icmphook_internal::g_socks.end()) {
|
||||
return false;
|
||||
}
|
||||
*out_result = icmphook_internal::bind_hook_ws2(s, name, namelen);
|
||||
return true;
|
||||
}
|
||||
|
||||
void icmphook_net_init() {
|
||||
icmphook_internal::install_icmphook_hooks();
|
||||
icmphook_iphlpapi_install();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <winsock2.h>
|
||||
|
||||
bool icmphook_is_emulated_socket(SOCKET s);
|
||||
|
||||
/*!
|
||||
* Handle bind() for emulated ICMP sockets: records interface address and succeeds without kernel bind.
|
||||
* Returns true if this socket was handled (caller should return 0).
|
||||
*/
|
||||
bool icmphook_try_bind(SOCKET s, const struct sockaddr *name, int namelen, int *out_result);
|
||||
|
||||
void icmphook_net_init();
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "util/fileutils.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "hooks/icmphook_net.h"
|
||||
|
||||
// hooking related stuff
|
||||
static decltype(GetAdaptersInfo) *GetAdaptersInfo_orig = nullptr;
|
||||
@@ -189,6 +190,11 @@ static int WINAPI bind_hook(SOCKET s, const struct sockaddr *name, int namelen)
|
||||
#pragma ide diagnostic ignored "OCDFAInspection"
|
||||
#endif
|
||||
|
||||
int icmp_bind_result = 0;
|
||||
if (icmphook_try_bind(s, name, namelen, &icmp_bind_result)) {
|
||||
return icmp_bind_result;
|
||||
}
|
||||
|
||||
// cast to sockaddr_in
|
||||
struct sockaddr_in *in_name = (struct sockaddr_in *) name;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <shlwapi.h>
|
||||
#include <windows.h>
|
||||
#include <cfg/configurator.h>
|
||||
|
||||
#include "api/modules/capture.h"
|
||||
@@ -76,6 +77,7 @@
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "hooks/lang.h"
|
||||
#include "hooks/networkhook.h"
|
||||
#include "hooks/icmphook_net.h"
|
||||
#include "hooks/unisintrhook.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "launcher/logger.h"
|
||||
@@ -244,6 +246,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
bool easrv_smart = false;
|
||||
bool load_stubs = false;
|
||||
bool netfix_disable = false;
|
||||
bool icmphook_enable = false;
|
||||
bool lang_disable = false;
|
||||
std::string process_priority_str = "high";
|
||||
bool cardio_enabled = false;
|
||||
@@ -299,6 +302,26 @@ int main_implementation(int argc, char *argv[]) {
|
||||
}
|
||||
auto &options = *options_ptr;
|
||||
|
||||
#if !SPICE_XP
|
||||
|
||||
{
|
||||
// skip elevation only if AutoElevate is explicitly set to "user"
|
||||
const bool skip_elevation = options[launcher::Options::AutoElevate].is_active() &&
|
||||
options[launcher::Options::AutoElevate].value_text() == "user";
|
||||
if (!skip_elevation && !sysutils::is_running_as_admin()) {
|
||||
log_info("launcher", "relaunching with administrator privileges");
|
||||
if (sysutils::relaunch_as_admin()) {
|
||||
exit(0);
|
||||
} else {
|
||||
// elevation failed or was denied by the user
|
||||
log_fatal("launcher", "failed to launch with administrator privileges");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !SPICE_XP
|
||||
|
||||
// check options
|
||||
// TODO: get rid of some booleans here and make use of the options directly
|
||||
if (options[launcher::Options::OpenConfigurator].value_bool()) {
|
||||
@@ -621,8 +644,11 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::GitaDoraCabinetType].is_active()) {
|
||||
games::gitadora::CAB_TYPE = options[launcher::Options::GitaDoraCabinetType].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::GitaDoraArenaSingleWindow].value_bool() && GRAPHICS_WINDOWED) {
|
||||
games::gitadora::ARENA_SINGLE_WINDOW = true;
|
||||
if (options[launcher::Options::GitaDoraArenaSingleWindow].value_bool()) {
|
||||
// for full screen
|
||||
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
||||
// for windowed
|
||||
GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
|
||||
}
|
||||
if (options[launcher::Options::GitaDoraWailHold].is_active()) {
|
||||
socd::TILT_HOLD_MS = options[launcher::Options::GitaDoraWailHold].value_uint32();
|
||||
@@ -732,6 +758,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::DisableNetworkFixes].value_bool()) {
|
||||
netfix_disable = true;
|
||||
}
|
||||
if (options[launcher::Options::EnableICMPHook].value_bool()) {
|
||||
icmphook_enable = true;
|
||||
}
|
||||
if (options[launcher::Options::DisableACPHook].value_bool()) {
|
||||
lang_disable = true;
|
||||
}
|
||||
@@ -889,6 +918,12 @@ int main_implementation(int argc, char *argv[]) {
|
||||
PIN_MACRO_ENABLED = true;
|
||||
PIN_MACRO_VALUES[1] = options[launcher::Options::Player2PinMacro].value_text();
|
||||
}
|
||||
if (options[launcher::Options::AutoPinMacroTrigger0].is_active()) {
|
||||
AUTO_PIN_MACRO_TRIGGER[0] = options[launcher::Options::AutoPinMacroTrigger0].value_text();
|
||||
}
|
||||
if (options[launcher::Options::AutoPinMacroTrigger1].is_active()) {
|
||||
AUTO_PIN_MACRO_TRIGGER[1] = options[launcher::Options::AutoPinMacroTrigger1].value_text();
|
||||
}
|
||||
|
||||
for (auto &reader : options[launcher::Options::ICCAReaderPort].values_text()) {
|
||||
static int reader_id = 0;
|
||||
@@ -1170,9 +1205,20 @@ int main_implementation(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// reflec beat touch emulation
|
||||
if (options[launcher::Options::spice2x_RBTouchScale].is_active()) {
|
||||
games::rb::TOUCH_SCALING = options[launcher::Options::spice2x_RBTouchScale].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::RBTouchSize].is_active()) {
|
||||
const auto text = options[launcher::Options::RBTouchSize].value_text();
|
||||
if (text == "3") {
|
||||
games::rb::TOUCH_SIZE = 3;
|
||||
}
|
||||
}
|
||||
if (options[launcher::Options::RBTouchPollRate].is_active()) {
|
||||
games::rb::TOUCH_POLL_RATE = options[launcher::Options::RBTouchPollRate].value_uint32();
|
||||
}
|
||||
|
||||
if (options[launcher::Options::spice2x_AsioForceUnload].value_bool()) {
|
||||
hooks::audio::ASIO_FORCE_UNLOAD_ON_STOP = true;
|
||||
}
|
||||
@@ -2259,6 +2305,11 @@ int main_implementation(int argc, char *argv[]) {
|
||||
avs::core::load_dll();
|
||||
avs::ea3::load_dll();
|
||||
|
||||
// ICMP emulation (opt-in; before games open raw ICMP sockets)
|
||||
if (icmphook_enable) {
|
||||
icmphook_net_init();
|
||||
}
|
||||
|
||||
// net fix
|
||||
if (!netfix_disable) {
|
||||
networkhook_init();
|
||||
|
||||
@@ -203,7 +203,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
{
|
||||
.title = "Show Cursor & Touch Emulation Enable",
|
||||
.name = "s",
|
||||
.desc = "Shows the cursor in the game window; also turns on touch emulation. Do not use if you use a real touch screen.",
|
||||
.desc = "Shows the cursor in the game window; also turns on touch emulation. Do not enable this if you have a real touch screen.",
|
||||
.type = OptionType::Bool,
|
||||
.category = "Common",
|
||||
},
|
||||
@@ -265,7 +265,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.title = "Full Screen Orientation Swap (EXPERIMENTAL)",
|
||||
.name = "forceresswap",
|
||||
.desc =
|
||||
"Allows you to play portrait games in in landscape (and vice versa) by transposing resolution and applying image scaling.\n\n"
|
||||
"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 "
|
||||
@@ -312,7 +312,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.name = "sp2x-dx9on12",
|
||||
.display_name = "dx9on12",
|
||||
.aliases= "dx9on12",
|
||||
.desc = "Use D3D9On12 wrapper library, requires Windows 10. Has no effect games on that don't use DX9. Can cause some games to crash.\n\n"
|
||||
.desc = "Use D3D9On12 wrapper library, requires Windows 10. Has no effect on games that don't use DX9. Can cause some games to crash.\n\n"
|
||||
"Default: auto (use DX9 for most games, but turn on 9on12 for games that require it on non-NVIDIA GPUs).",
|
||||
.type = OptionType::Enum,
|
||||
.category = "Graphics (Common)",
|
||||
@@ -1038,9 +1038,10 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
{
|
||||
.title = "GitaDora Two Channel Audio",
|
||||
.title = "GitaDora Two Channel Audio (DX/SD only)",
|
||||
.name = "2ch",
|
||||
.desc = "Attempt to reduce audio channels down to just two channels.",
|
||||
.desc = "Attempt to reduce audio channels down to just two channels. "
|
||||
"This option does nothing for Arena Model (GW Delta and above); need a patch for that.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "GitaDora",
|
||||
.category = "Game Options",
|
||||
@@ -1056,9 +1057,12 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
},
|
||||
{
|
||||
// GitaDoraArenaSingleWindow
|
||||
.title = "GitaDora Arena Single Window (EXPERIMENTAL)",
|
||||
.title = "GitaDora Arena Disable Subscreens (EXPERIMENTAL)",
|
||||
.name = "gdaonewindow",
|
||||
.desc = "For Arena Model, when combined with windowed mode, only show the main game window; enables subscreen overlay.",
|
||||
.desc = "For Arena Model:\n\n"
|
||||
"Windowed mode: instead of 4 windows, create 1 window.\n\n"
|
||||
"Fullscreen mode: instead of requiring 4 monitors, use only the primary monitor. WARNING: requires 4K monitor.\n\n"
|
||||
"To access the subscreen, use the subscreen overlay.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "GitaDora",
|
||||
.category = "Game Options",
|
||||
@@ -1069,8 +1073,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.name = "gdlefty",
|
||||
.desc = "Enables lefty mode, flipping motion sensor directions. Default: off.\n\n"
|
||||
"Without this option, enabling LEFT in the game option will continuously trigger UP wail.\n\n"
|
||||
"Has no effect if you are using analog bindings for X/Y axis; expectation is that your controller "
|
||||
"handles this correctly (most do not, however).\n\n"
|
||||
"Has no effect if you are using analog bindings for X/Y axis; most controllers do not handle this correctly.\n\n"
|
||||
"As always, remember to restart the game after changing options. If you need to change in the game, "
|
||||
"use the I/O panel overlay window.",
|
||||
.type = OptionType::Enum,
|
||||
@@ -1268,7 +1271,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
{
|
||||
.title = "Force Load LovePlus Module",
|
||||
.name = "loveplus",
|
||||
.desc = "manually enable LovePlus module.",
|
||||
.desc = "Manually enable LovePlus module.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "LovePlus",
|
||||
.category = "Game Options (Advanced)",
|
||||
@@ -1276,7 +1279,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
{
|
||||
.title = "Force Load Charge Machine Module",
|
||||
.name = "pcm",
|
||||
.desc = "manually enable Charge Machine module.",
|
||||
.desc = "Manually enable Charge Machine module.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Charge Machine",
|
||||
.category = "Game Options (Advanced)",
|
||||
@@ -1284,7 +1287,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
{
|
||||
.title = "Force Load Ongaku Paradise Module",
|
||||
.name = "onpara",
|
||||
.desc = "manually enable Ongaku Paradise module.",
|
||||
.desc = "Manually enable Ongaku Paradise module.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Ongaku Paradise",
|
||||
.category = "Game Options (Advanced)",
|
||||
@@ -1292,7 +1295,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
{
|
||||
.title = "Force Load Busou Shinki Module",
|
||||
.name = "busou",
|
||||
.desc = "manually enable Busou Shinki module.",
|
||||
.desc = "Manually enable Busou Shinki module.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Busou Shinki: Armored Princess Battle Conductor",
|
||||
.category = "Game Options (Advanced)",
|
||||
@@ -1301,7 +1304,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
// LoadCCJModule
|
||||
.title = "Force Load Chase Chase Jokers Module",
|
||||
.name = "ccj",
|
||||
.desc = "manually enable Chase Chase Jokers module.",
|
||||
.desc = "Manually enable Chase Chase Jokers module.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Chase Chase Jokers",
|
||||
.category = "Game Options (Advanced)",
|
||||
@@ -1310,7 +1313,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
// LoadQKSModule
|
||||
.title = "Force Load QuizKnock STADIUM Module",
|
||||
.name = "qks",
|
||||
.desc = "manually enable QuizKnock STADIUM module",
|
||||
.desc = "Manually enable QuizKnock STADIUM module.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "QuizKnock STADIUM",
|
||||
.category = "Game Options (Advanced)",
|
||||
@@ -1319,7 +1322,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
// LoadMFGModule
|
||||
.title = "Force Load Mahjong Fight Girl Module",
|
||||
.name = "mfg",
|
||||
.desc = "manually enable Mahjong Fight Girl module.",
|
||||
.desc = "Manually enable Mahjong Fight Girl module.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Mahjong Fight Girl",
|
||||
.category = "Game Options (Advanced)",
|
||||
@@ -1328,7 +1331,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
// LoadPCModule
|
||||
.title = "Force Load Polaris Chord Module",
|
||||
.name = "pc",
|
||||
.desc = "manually enable Polaris Chord module.",
|
||||
.desc = "Manually enable Polaris Chord module.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Polaris Chord",
|
||||
.category = "Game Options (Advanced)",
|
||||
@@ -1337,7 +1340,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
// LoadMusecaModule
|
||||
.title = "Force Load Museca Module",
|
||||
.name = "museca",
|
||||
.desc = "manually enable Museca module.",
|
||||
.desc = "Manually enable Museca module.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Museca",
|
||||
.category = "Game Options (Advanced)",
|
||||
@@ -1461,7 +1464,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
{
|
||||
.title = "API Verbose Logging",
|
||||
.name = "apilogging",
|
||||
.desc = "verbose logging of API activity.",
|
||||
.desc = "Verbose logging of API activity.",
|
||||
.type = OptionType::Bool,
|
||||
.category = "API (Dev)",
|
||||
},
|
||||
@@ -1584,8 +1587,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.title = "Force Touch Emulation",
|
||||
.name = "touchemuforce",
|
||||
.desc = "Force enable hook for GetTouchInputInfo API and inject WM_TOUCH events. "
|
||||
"This is automatically turned on when needed, so it is not typically required to "
|
||||
"turn it on manually. Do not enable this unless you have trouble.",
|
||||
"This is automatically enabled when needed and is typically not required. Do not enable this unless you have trouble.",
|
||||
.type = OptionType::Bool,
|
||||
.category = "Touch Parameters",
|
||||
},
|
||||
@@ -1688,7 +1690,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
"legacy: Preserve buggy old behavior (Default)\n\n"
|
||||
"fix: Add E00401 to non-FeliCa cards, scan FeliCa cards as-is (Recommended for most users)\n\n"
|
||||
"all: Add E00401 to all cards, including FeliCa cards (For really old games only)\n\n"
|
||||
"The algorithm is simple; the prefix is applied, the scanned card number is appended upto 8 bytes, and remaining digits are discarded. "
|
||||
"The algorithm is simple; the prefix is applied, the scanned card number is appended up to 8 bytes, and remaining digits are discarded. "
|
||||
"For example, abcd56780123 is converted to e00401abcd567801 on card in. This results in different card numbers so "
|
||||
"be careful when connecting to servers!",
|
||||
.type = OptionType::Enum,
|
||||
@@ -1719,7 +1721,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
{
|
||||
.title = "Realtime Process Priority (DEPRECATED - use -processpriority instead)",
|
||||
.name = "realtime",
|
||||
.desc = "Sets the process priority to realtime, can help with odd lag spikes.",
|
||||
.desc = "Sets the process priority to realtime; can help with odd lag spikes.",
|
||||
.type = OptionType::Bool,
|
||||
.hidden = true,
|
||||
.category = "Performance",
|
||||
@@ -1730,7 +1732,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.name = "sp2x-processpriority",
|
||||
.display_name = "processpriority",
|
||||
.aliases= "processpriority",
|
||||
.desc = "Sets the process priority, can help with odd lag spikes. Default: high.",
|
||||
.desc = "Sets the process priority, which can help with odd lag spikes. Default: high.",
|
||||
.type = OptionType::Enum,
|
||||
.category = "Performance",
|
||||
.elements = {
|
||||
@@ -1807,7 +1809,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Disables all audio hooks, including device initialization and volume hooks.\n\n"
|
||||
"Default: off (allow Spice to hook IMMDeviceEnumerator for and optionally let "
|
||||
"you use a different backend instead of exclusive WASAPI).\n\n"
|
||||
"Check this if you want games to natively access your audio device.",
|
||||
"Check this to allow games to natively access your audio device.",
|
||||
.type = OptionType::Bool,
|
||||
.category = "Audio (Hacks)",
|
||||
},
|
||||
@@ -1819,7 +1821,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.aliases= "volumehookdisable",
|
||||
.desc = "Disables audio volume hook.\n\n"
|
||||
"Default: off (prevent games from changing audio volume by hooking IAudioEndpointVolume).\n\n"
|
||||
"Check this if you want games to freely change your volume.",
|
||||
"Check this to allow games to freely change your volume.",
|
||||
.type = OptionType::Bool,
|
||||
.category = "Audio (Hacks)",
|
||||
},
|
||||
@@ -1961,7 +1963,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
{
|
||||
.title = "Blocking Logger",
|
||||
.name = "logblock",
|
||||
.desc = "Slower but safer logging used for debugging.",
|
||||
.desc = "Slower but safer logging for debugging.",
|
||||
.type = OptionType::Bool,
|
||||
.category = "Debug Log",
|
||||
},
|
||||
@@ -2301,7 +2303,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
{
|
||||
.title = "JB Touch Algorithm",
|
||||
.name = "jubeattouchalgo",
|
||||
.desc = "For touch screen players - choose the touch algorithm to use.\n\n"
|
||||
.desc = "For touch screen players: choose the touch algorithm to use.\n\n"
|
||||
"legacy - evenly divide the grid into 16 squares; old spicetools behavior, slightly inaccurate in gaps\n\n"
|
||||
"improved (default) - squares register as-is, gaps will trigger the closest square\n\n"
|
||||
"accurate - only touches within squares will trigger; gaps do nothing (for AC size touch screens)",
|
||||
@@ -2316,7 +2318,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
},
|
||||
{
|
||||
// spice2x_RBTouchScale
|
||||
.title = "RB Scale Touch Input",
|
||||
.title = "RB Touch Emulation Scale",
|
||||
.name = "sp2x-rbscaletouch",
|
||||
.display_name = "rbscaletouch",
|
||||
.aliases= "rbscaletouch",
|
||||
@@ -2326,6 +2328,31 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.game_name = "Reflec Beat",
|
||||
.category = "Game Options",
|
||||
},
|
||||
{
|
||||
// RBTouchSize
|
||||
.title = "RB Touch Emulation Size",
|
||||
.name = "rbtouchsize",
|
||||
.desc = "Size of the touch area; how many IR sensors a single finger activates. Default: 1 (1x1).",
|
||||
.type = OptionType::Enum,
|
||||
.game_name = "Reflec Beat",
|
||||
.category = "Game Options",
|
||||
.elements = {
|
||||
{"1", "1x1"},
|
||||
{"3", "3x3"},
|
||||
},
|
||||
},
|
||||
{
|
||||
// RBTouchPollRate
|
||||
.title = "RB Touch Emulation Poll Hz",
|
||||
.name = "rbtouchhz",
|
||||
.desc = "By default, the game polls for touch at 120Hz. "
|
||||
"This option overrides that rate; enter a number betwen 1 and 1000.\n\n"
|
||||
"It should be noted that higher poll does not necessarily improve accuracy or performance.",
|
||||
.type = OptionType::Integer,
|
||||
.setting_name = "250",
|
||||
.game_name = "Reflec Beat",
|
||||
.category = "Game Options",
|
||||
},
|
||||
{
|
||||
// spice2x_AsioForceUnload
|
||||
.title = "ASIO Force Unload On Stop",
|
||||
@@ -2463,6 +2490,28 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
{"both", ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
// AutoPinMacroTrigger0
|
||||
.title = "Auto PIN Macro Trigger Log String (P1)",
|
||||
.name = "autopinmacrotrigger0",
|
||||
.desc =
|
||||
"Substring matched against game log output to detect when Player 1's PIN entry "
|
||||
"screen is ready. When the substring appears in any log line, the PIN macro is "
|
||||
"typed for Player 1 only. Leave blank to disable auto-trigger for P1.",
|
||||
.type = OptionType::Text,
|
||||
.category = "Network (Advanced)",
|
||||
},
|
||||
{
|
||||
// AutoPinMacroTrigger1
|
||||
.title = "Auto PIN Macro Trigger Log String (P2)",
|
||||
.name = "autopinmacrotrigger1",
|
||||
.desc =
|
||||
"Substring matched against game log output to detect when Player 2's PIN entry "
|
||||
"screen is ready. When the substring appears in any log line, the PIN macro is "
|
||||
"typed for Player 2 only. Leave blank to disable auto-trigger for P2.",
|
||||
.type = OptionType::Text,
|
||||
.category = "Network (Advanced)",
|
||||
},
|
||||
{
|
||||
// spice2x_LowLatencySharedAudio
|
||||
.title = "Low Latency Shared Audio",
|
||||
@@ -2502,7 +2551,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.aliases= "nonvapi",
|
||||
.desc = "Completely block the game from accessing NVAPI. Can be used if NVAPI is returning undesirable "
|
||||
"results to the game (e.g., wrong values from NvDisplayConfig for SDVX). You may need to apply additional "
|
||||
"hex edits in order to boot the game correctly.",
|
||||
"hex edits to boot the game correctly.",
|
||||
.type = OptionType::Bool,
|
||||
.category = "Graphics (Common)",
|
||||
},
|
||||
@@ -2550,7 +2599,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.name = "sp2x-smx-stage",
|
||||
.display_name = "smxstage",
|
||||
.aliases= "smxstage",
|
||||
.desc = "StepmaniaX stage will show up as a device that can recieve lighting output. "
|
||||
.desc = "StepmaniaX stage will show up as a device that can receive lighting output. "
|
||||
"For configurator, restart spicecfg.exe after enabling this to have the device show up for binding.",
|
||||
.type = OptionType::Bool,
|
||||
.category = "I/O Options",
|
||||
@@ -2561,7 +2610,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.name = "sp2x-smx-dedicab",
|
||||
.display_name = "smxdedicab",
|
||||
.aliases = "smxdedicab",
|
||||
.desc = "StepManiaX Dedicated Cabinet will show up as a device that can recieve lighting output. "
|
||||
.desc = "StepManiaX Dedicated Cabinet will show up as a device that can receive lighting output. "
|
||||
"For configurator, restart spicecfg.exe after enabling this to have the device show up for binding.",
|
||||
.type = OptionType::Bool,
|
||||
.category = "I/O Options"
|
||||
@@ -2665,7 +2714,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
"Enables emulation of touch gestures to make it easier to navigate the game without a touchscreen.\n\n"
|
||||
"This enables two things:\n"
|
||||
" 1. Swipe/Touch bindings in Buttons tab (Swipe Next Page, etc)\n"
|
||||
" 2. PIN pad bindings (make sure Unscramble Keypad patch is applied)).",
|
||||
" 2. PIN pad bindings (make sure Unscramble Keypad patch is applied).",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Nostalgia",
|
||||
.category = "Game Options",
|
||||
@@ -2805,6 +2854,31 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Bool,
|
||||
.category = "Development"
|
||||
},
|
||||
{
|
||||
// EnableICMPHook
|
||||
.title = "Enable ICMP Emulation",
|
||||
.name = "icmphook",
|
||||
.desc = "Emulate keepalive ping replies in user mode, so the game does not need to "
|
||||
"open privileged raw ICMP sockets.",
|
||||
.type = OptionType::Bool,
|
||||
.category = "Network (Development)",
|
||||
},
|
||||
{
|
||||
// AutoElevate
|
||||
.title = "Run as",
|
||||
.name = "runas",
|
||||
.desc = "Controls whether spice will automatically re-launch with administrator privileges at startup.\n\n"
|
||||
"admin (default): automatically re-launch with administrator privileges, "
|
||||
"needed for most games to function properly\n\n"
|
||||
"user: skip elevation, run with current user privileges; game may fail to launch, "
|
||||
"crash, or critical features may silently fail; use at your own risk.",
|
||||
.type = OptionType::Enum,
|
||||
.category = "Development",
|
||||
.elements = {
|
||||
{"admin", ""},
|
||||
{"user", ""},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const std::vector<std::string> &launcher::get_categories(Options::OptionsCategory category) {
|
||||
|
||||
@@ -242,6 +242,8 @@ namespace launcher {
|
||||
spice2x_JubeatLegacyTouch,
|
||||
JubeatTouchAlgo,
|
||||
spice2x_RBTouchScale,
|
||||
RBTouchSize,
|
||||
RBTouchPollRate,
|
||||
spice2x_AsioForceUnload,
|
||||
spice2x_IIDXNoESpec,
|
||||
spice2x_IIDXWindowedTDJ,
|
||||
@@ -253,6 +255,8 @@ namespace launcher {
|
||||
IIDXSubMonitorOverride,
|
||||
spice2x_IIDXEmulateSubscreenKeypadTouch,
|
||||
spice2x_AutoCard,
|
||||
AutoPinMacroTrigger0,
|
||||
AutoPinMacroTrigger1,
|
||||
spice2x_LowLatencySharedAudio,
|
||||
spice2x_TapeLedAlgorithm,
|
||||
spice2x_NoNVAPI,
|
||||
@@ -282,6 +286,8 @@ namespace launcher {
|
||||
OptionConflictResolution,
|
||||
OtocaCamHook,
|
||||
DisableHighResTimer,
|
||||
EnableICMPHook,
|
||||
AutoElevate,
|
||||
};
|
||||
|
||||
enum class OptionsCategory {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "eamuse.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
|
||||
@@ -44,6 +45,9 @@ static uint8_t AUTO_INSERT_CARD_CACHED_DATA[2][8];
|
||||
// pin macro
|
||||
bool PIN_MACRO_ENABLED = false;
|
||||
std::string PIN_MACRO_VALUES[2] = {"", ""};
|
||||
std::string AUTO_PIN_MACRO_TRIGGER[2];
|
||||
static std::atomic_bool AUTO_PIN_MACRO_REQUEST[2] {false, false};
|
||||
static bool AUTO_PIN_MACRO_PLAYER_ACTIVE[2] = {false, false};
|
||||
static std::thread *PIN_MACRO_THREAD = nullptr;
|
||||
static bool PIN_MACRO_THREAD_ACTIVE = false;
|
||||
static uint16_t PIN_MACRO_TRIGGER_KEYS[2] = {
|
||||
@@ -51,6 +55,18 @@ static uint16_t PIN_MACRO_TRIGGER_KEYS[2] = {
|
||||
games::OverlayButtons::TriggerPinMacroP2
|
||||
};
|
||||
|
||||
static bool pin_macro_log_hook(void *, const std::string &data, logger::Style, std::string &) {
|
||||
for (int unit = 0; unit < 2; unit++) {
|
||||
if (!AUTO_PIN_MACRO_PLAYER_ACTIVE[unit]) {
|
||||
continue;
|
||||
}
|
||||
if (data.find(AUTO_PIN_MACRO_TRIGGER[unit]) != std::string::npos) {
|
||||
AUTO_PIN_MACRO_REQUEST[unit].store(true);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool eamuse_get_card(int active_count, int unit_id, uint8_t *card) {
|
||||
|
||||
// get unit index
|
||||
@@ -324,6 +340,25 @@ void eamuse_pin_macro_start_thread() {
|
||||
// set active
|
||||
PIN_MACRO_THREAD_ACTIVE = true;
|
||||
|
||||
// a unit is eligible for auto-trigger only if all the static prerequisites are
|
||||
// satisfied at startup; precomputing this lets the log hook skip per-line checks
|
||||
// on values that never change at runtime
|
||||
for (int unit = 0; unit < 2; unit++) {
|
||||
AUTO_PIN_MACRO_PLAYER_ACTIVE[unit] =
|
||||
!AUTO_PIN_MACRO_TRIGGER[unit].empty() &&
|
||||
!PIN_MACRO_VALUES[unit].empty();
|
||||
if(!AUTO_PIN_MACRO_TRIGGER[unit].empty() && PIN_MACRO_VALUES[unit].empty()) {
|
||||
log_warning("eamuse", "Configuration error; Pin Macro empty for P{}.", unit+1);
|
||||
}
|
||||
}
|
||||
|
||||
// register scene log hook so the macro fires on the per-game trigger string,
|
||||
// but only if at least one player is eligible
|
||||
if (AUTO_PIN_MACRO_PLAYER_ACTIVE[0] || AUTO_PIN_MACRO_PLAYER_ACTIVE[1]) {
|
||||
logger::hook_add(pin_macro_log_hook, nullptr);
|
||||
log_info("eamuse", "AUTO_PIN_MACRO enabled");
|
||||
}
|
||||
|
||||
// create thread
|
||||
PIN_MACRO_THREAD = new std::thread([]() {
|
||||
uint16_t keypad_overrides[] = {
|
||||
@@ -345,15 +380,20 @@ void eamuse_pin_macro_start_thread() {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
while (PIN_MACRO_THREAD_ACTIVE) {
|
||||
// wait for key press
|
||||
// wait for key press or auto-trigger
|
||||
if (!active_unit.has_value()) {
|
||||
for (int unit = 0; unit < 2; unit++) {
|
||||
if (PIN_MACRO_VALUES[unit].empty()) {
|
||||
continue;
|
||||
}
|
||||
if (overlay_buttons &&
|
||||
bool key_press = overlay_buttons &&
|
||||
(!overlay::OVERLAY || overlay::OVERLAY->hotkeys_triggered()) &&
|
||||
GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(PIN_MACRO_TRIGGER_KEYS[unit]))) {
|
||||
GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(PIN_MACRO_TRIGGER_KEYS[unit]));
|
||||
bool auto_request = AUTO_PIN_MACRO_REQUEST[unit].exchange(false);
|
||||
if (auto_request) {
|
||||
log_info("eamuse", "AUTO_PIN_MACRO_REQUEST detected for P{}", unit+1);
|
||||
}
|
||||
if (key_press || auto_request) {
|
||||
active_unit = unit;
|
||||
// Reset key index
|
||||
pin_index[unit] = 0;
|
||||
@@ -405,6 +445,9 @@ void eamuse_pin_macro_stop_thread() {
|
||||
delete PIN_MACRO_THREAD;
|
||||
PIN_MACRO_THREAD = nullptr;
|
||||
}
|
||||
if (AUTO_PIN_MACRO_PLAYER_ACTIVE[0] || AUTO_PIN_MACRO_PLAYER_ACTIVE[1]) {
|
||||
logger::hook_remove(pin_macro_log_hook, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void eamuse_set_keypad_overrides(size_t unit, uint16_t keypad_state) {
|
||||
|
||||
@@ -38,6 +38,7 @@ extern float AUTO_INSERT_CARD_COOLDOWN;
|
||||
|
||||
extern bool PIN_MACRO_ENABLED;
|
||||
extern std::string PIN_MACRO_VALUES[2];
|
||||
extern std::string AUTO_PIN_MACRO_TRIGGER[2];
|
||||
|
||||
bool eamuse_get_card(int active_count, int unit_id, uint8_t *card);
|
||||
bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int unit_id);
|
||||
|
||||
@@ -416,7 +416,7 @@ namespace wintouchemu {
|
||||
// same as iidx case above
|
||||
log_info("wintouchemu", "use mouse cursor API for popn overlay subscreen");
|
||||
USE_MOUSE = true;
|
||||
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
|
||||
} else if (games::gitadora::is_arena_model() && GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
||||
log_info("wintouchemu", "use mouse cursor API for gitadora overlay subscreen");
|
||||
USE_MOUSE = true;
|
||||
} else {
|
||||
|
||||
@@ -415,7 +415,7 @@ void overlay::SpiceOverlay::init() {
|
||||
window_sub = new overlay::windows::DRSDanceFloorDisplay(this);
|
||||
} else if (avs::game::is_model("KFC")) {
|
||||
window_sub = new overlay::windows::SDVXSubScreen(this);
|
||||
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
|
||||
} else if (games::gitadora::is_arena_model()) {
|
||||
window_sub = new overlay::windows::GitaDoraSubScreen(this);
|
||||
} else if (games::popn::is_pikapika_model()) {
|
||||
window_sub = new overlay::windows::PopnSubScreen(this);
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace overlay::windows {
|
||||
sub = "Show Valkyrie Subscreen";
|
||||
} else if (avs::game::is_model("REC")) {
|
||||
sub = "Show DRS Dance Floor";
|
||||
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
|
||||
} else if (games::gitadora::is_arena_model()) {
|
||||
sub = "Show GITADORA Subscreen";
|
||||
} else if (games::popn::is_pikapika_model()) {
|
||||
sub = "Show Pop'n Subscreen";
|
||||
|
||||
@@ -11,6 +11,12 @@ namespace overlay::windows {
|
||||
|
||||
if (!games::gitadora::is_arena_model()) {
|
||||
this->disabled_message = "Requires Arena Model!";
|
||||
} else if (!GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
this->disabled_message = "Use the dedicated subscreen window!";
|
||||
} else {
|
||||
this->disabled_message = "Subscreen overlay disabled by user; enable single window mode for overlay.";
|
||||
}
|
||||
}
|
||||
|
||||
this->resize_callback = keep_10_by_16;
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#include <windows.h>
|
||||
#undef WIN32_NO_STATUS
|
||||
|
||||
#include <shellapi.h>
|
||||
#include <shlwapi.h>
|
||||
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "avs/game.h"
|
||||
#include "util/detour.h"
|
||||
@@ -585,4 +588,56 @@ namespace sysutils {
|
||||
(void*)EnumDisplayDevicesA_hook,
|
||||
(void**)&EnumDisplayDevicesA_orig);
|
||||
}
|
||||
|
||||
#if !SPICE_XP
|
||||
|
||||
bool is_running_as_admin() {
|
||||
HANDLE token_handle = nullptr;
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TOKEN_ELEVATION elevation = {};
|
||||
DWORD size = sizeof(elevation);
|
||||
const BOOL ok = GetTokenInformation(
|
||||
token_handle, TokenElevation, &elevation, size, &size);
|
||||
CloseHandle(token_handle);
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
return elevation.TokenIsElevated != 0;
|
||||
}
|
||||
|
||||
bool relaunch_as_admin() {
|
||||
// get the current executable path
|
||||
wchar_t executable_path[MAX_PATH];
|
||||
if (!GetModuleFileNameW(nullptr, executable_path, MAX_PATH)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the current working directory to pass to the elevated process
|
||||
wchar_t working_dir[MAX_PATH];
|
||||
if (!GetCurrentDirectoryW(MAX_PATH, working_dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// skip past argv[0] in the original command line
|
||||
const wchar_t *args = PathGetArgsW(GetCommandLineW());
|
||||
|
||||
// use ShellExecuteW with "runas" verb to elevate
|
||||
HINSTANCE result = ShellExecuteW(
|
||||
nullptr,
|
||||
L"runas",
|
||||
executable_path,
|
||||
args,
|
||||
working_dir,
|
||||
SW_SHOW
|
||||
);
|
||||
|
||||
// ShellExecute returns > 32 on success
|
||||
return (intptr_t)result > 32;
|
||||
}
|
||||
|
||||
#endif // !SPICE_XP
|
||||
}
|
||||
|
||||
@@ -21,5 +21,12 @@ namespace sysutils {
|
||||
const std::vector<MonitorEntry> &enumerate_monitors();
|
||||
|
||||
extern std::string SECOND_MONITOR_OVERRIDE;
|
||||
void hook_EnumDisplayDevicesA();
|
||||
}
|
||||
void hook_EnumDisplayDevicesA();
|
||||
|
||||
#if !SPICE_XP
|
||||
|
||||
bool is_running_as_admin();
|
||||
bool relaunch_as_admin();
|
||||
|
||||
#endif // !SPICE_XP
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user