mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b930ad95f | |||
| 3c60f3966b | |||
| c59d399ab8 | |||
| f69e40fa26 | |||
| e9dcc5717e | |||
| 5bec3d5db7 | |||
| 6ec2b47345 | |||
| ebda16beae | |||
| c29a26f183 | |||
| 2014e9cf75 | |||
| 33f2e1d4b7 | |||
| eb037542b4 | |||
| 039b42ad53 | |||
| 33b4718744 | |||
| 71ba9b6b47 | |||
| 1957917270 | |||
| b9ccddf47d | |||
| cd0ba51b5a | |||
| 1f23d88c7a | |||
| 46e43ab09c | |||
| 96950b6b4e |
@@ -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
|
||||
@@ -607,6 +609,9 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
reader/structuredmessage.cpp
|
||||
reader/crypt.cpp
|
||||
|
||||
# sdk
|
||||
sdk/sdk.cpp
|
||||
|
||||
# stubs
|
||||
stubs/stubs.cpp
|
||||
|
||||
@@ -633,6 +638,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
util/time.cpp
|
||||
util/cpuutils.cpp
|
||||
util/netutils.cpp
|
||||
util/precise_timer.cpp
|
||||
util/sysutils.cpp
|
||||
util/lz77.cpp
|
||||
util/tapeled.cpp
|
||||
@@ -860,6 +866,36 @@ if(NOT MSVC)
|
||||
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
|
||||
endif()
|
||||
|
||||
# sdk_sample_v0_flat_c.dll (32 bit)
|
||||
set(SOURCE_FILES sdk/sample/v0/flat_c/v0_flat_c.c)
|
||||
add_library(spicetools_sdk_sample_v0_flat_c_32 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/flat_c/v0_flat_c.def)
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES OUTPUT_NAME "sdk_sample_v0_flat_c")
|
||||
|
||||
if(NOT MSVC)
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
|
||||
endif()
|
||||
|
||||
# sdk_sample_v0_flat_c.dll (64 bit)
|
||||
set(SOURCE_FILES sdk/sample/v0/flat_c/v0_flat_c.c)
|
||||
add_library(spicetools_sdk_sample_v0_flat_c_64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/flat_c/v0_flat_c.def)
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES OUTPUT_NAME "sdk_sample_v0_flat_c")
|
||||
|
||||
if(NOT MSVC)
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
|
||||
endif()
|
||||
|
||||
# sdk_sample_v0_cpp.dll (64 bit)
|
||||
set(SOURCE_FILES sdk/sample/v0/cpp/v0_cpp.cpp)
|
||||
add_library(spicetools_sdk_sample_v0_cpp_64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/cpp/v0_cpp.def)
|
||||
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES OUTPUT_NAME "sdk_sample_v0_cpp")
|
||||
|
||||
if(NOT MSVC)
|
||||
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
|
||||
endif()
|
||||
|
||||
# output directories
|
||||
####################
|
||||
|
||||
@@ -869,14 +905,14 @@ set_target_properties(spicetools_cfg spicetools_cfg_linux
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools")
|
||||
|
||||
# output 32bit
|
||||
set_target_properties(spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_kbt spicetools_stubs_kld spicetools_stubs_cpusbxpkm
|
||||
set_target_properties(spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_kbt spicetools_stubs_kld spicetools_stubs_cpusbxpkm spicetools_sdk_sample_v0_flat_c_32
|
||||
PROPERTIES
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive32"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32")
|
||||
|
||||
# output 64bit
|
||||
set_target_properties(spicetools_spice64 spicetools_spice64_linux spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvcuda spicetools_stubs_nvcuvid spicetools_stubs_nvEncodeAPI64
|
||||
set_target_properties(spicetools_spice64 spicetools_spice64_linux spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvcuda spicetools_stubs_nvcuvid spicetools_stubs_nvEncodeAPI64 spicetools_sdk_sample_v0_flat_c_64 spicetools_sdk_sample_v0_cpp_64
|
||||
PROPERTIES
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive64"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64"
|
||||
|
||||
@@ -283,6 +283,16 @@ Python is the only one that is fully spec compliant.
|
||||
Other libraries may be missing features and contain bugs; please feel free to
|
||||
contribute code to fill the gaps if you work on a project using these libraries.
|
||||
|
||||
## Spice SDK (for DLL hooks / plugins)
|
||||
|
||||
As an alternative to Spice API, Spice SDK is available.
|
||||
|
||||
This is a flat C, header-only library that can be included in your DLL. Once
|
||||
initialized, you can call directly into helper routines provided by Spicetools
|
||||
executable, making low-latency and high-throughput interactions possible.
|
||||
|
||||
Check the documentation on the wiki for more details.
|
||||
|
||||
## License
|
||||
Unless otherwise noted, all files are licensed under the GPLv3.
|
||||
See the LICENSE file for the full license text.
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
#include "mdxf.h"
|
||||
#include "mdxf_poll.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/ddr/ddr.h"
|
||||
#include "games/ddr/io.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
#include <mutex>
|
||||
|
||||
#define MDFX_DEBUG_VERBOSE 0
|
||||
|
||||
@@ -109,9 +111,10 @@ static void mdxf_thread_start() {
|
||||
MDXF_THREAD = std::thread([] {
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
while (MDXF_THREAD_RUNNING.load(std::memory_order_acquire)) {
|
||||
mdxf_poll(false);
|
||||
std::this_thread::sleep_for(THREAD_PERIOD);
|
||||
timer.sleep(THREAD_PERIOD);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -512,4 +515,4 @@ acio::MDXFModule::~MDXFModule() {
|
||||
if (IS_THREAD_NEEDED) {
|
||||
mdxf_thread_stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "util/circular_buffer.h"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
using namespace acioemu;
|
||||
@@ -46,11 +47,12 @@ ICCADevice::ICCADevice(bool flip_order, bool keypad_thread, uint8_t node_count)
|
||||
// keypad thread for faster polling
|
||||
if (keypad_thread) {
|
||||
this->keypad_thread = new std::thread([this]() {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
while (this->cards) {
|
||||
for (int unit = 0; unit < this->node_count; unit++) {
|
||||
this->update_keypad(unit, false);
|
||||
}
|
||||
Sleep(7);
|
||||
timer.sleep(7);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "launcher/launcher.h"
|
||||
#include "games/io.h"
|
||||
#include "util/utils.h"
|
||||
#include "acio/mdxf/mdxf_poll.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
using namespace rapidjson;
|
||||
@@ -104,6 +105,7 @@ namespace api::modules {
|
||||
for (auto &analog : *this->analogs) {
|
||||
analog.override_enabled = false;
|
||||
}
|
||||
mdxf_poll(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -148,6 +150,7 @@ namespace api::modules {
|
||||
if (analog.getName() == name) {
|
||||
analog.override_state = CLAMP(state, 0.f, 1.f);
|
||||
analog.override_enabled = true;
|
||||
mdxf_poll(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -167,6 +170,7 @@ namespace api::modules {
|
||||
for (auto &analog : *this->analogs) {
|
||||
if (analog.getName() == name) {
|
||||
analog.override_enabled = false;
|
||||
mdxf_poll(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "launcher/launcher.h"
|
||||
#include "games/io.h"
|
||||
#include "util/utils.h"
|
||||
#include "acio/mdxf/mdxf_poll.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
using namespace rapidjson;
|
||||
@@ -107,6 +108,7 @@ namespace api::modules {
|
||||
for (auto &button : *this->buttons) {
|
||||
button.override_enabled = false;
|
||||
}
|
||||
mdxf_poll(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -153,6 +155,7 @@ namespace api::modules {
|
||||
GameAPI::Buttons::BUTTON_PRESSED : GameAPI::Buttons::BUTTON_NOT_PRESSED;
|
||||
button.override_velocity = CLAMP(state, 0.f, 1.f);
|
||||
button.override_enabled = true;
|
||||
mdxf_poll(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -172,6 +175,7 @@ namespace api::modules {
|
||||
for (auto &button : *this->buttons) {
|
||||
if (button.getName() == name) {
|
||||
button.override_enabled = false;
|
||||
mdxf_poll(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "avs/game.h"
|
||||
#include "external/rapidjson/document.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
using namespace rapidjson;
|
||||
@@ -58,6 +59,7 @@ namespace api::modules {
|
||||
// get params
|
||||
auto keypad = req.params[0].GetUint();
|
||||
auto input = std::string(req.params[1].GetString());
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// process all chars
|
||||
for (auto c : input) {
|
||||
@@ -91,11 +93,11 @@ namespace api::modules {
|
||||
|
||||
// set
|
||||
eamuse_set_keypad_overrides(keypad, state);
|
||||
Sleep(sleep_time);
|
||||
timer.sleep(sleep_time);
|
||||
|
||||
// unset
|
||||
eamuse_set_keypad_overrides(keypad, 0);
|
||||
Sleep(sleep_time);
|
||||
timer.sleep(sleep_time);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
|
||||
@@ -16,6 +17,7 @@ namespace api {
|
||||
controller->init_state(this->state);
|
||||
this->thread = new std::thread([this] () {
|
||||
log_warning("api::serial", "listening on {} (baud: {})", this->port, this->baud);
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// read buffer
|
||||
uint8_t read_buffer[16*1024];
|
||||
@@ -160,7 +162,7 @@ namespace api {
|
||||
|
||||
// slow down on reconnect
|
||||
if (this->running) {
|
||||
Sleep(retry_time);
|
||||
timer.sleep(retry_time);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
<button name="VEFX" vkey="10" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
</buttons>
|
||||
<analogs>
|
||||
<analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="P1 1" devid="Player 1" index="0"/>
|
||||
@@ -117,8 +117,8 @@
|
||||
<keypad_buttons/>
|
||||
<!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. -->
|
||||
<analogs>
|
||||
<analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1"/>
|
||||
<analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1"/>
|
||||
<analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="P1 1" devid="arcin (1p)" index="0"/>
|
||||
@@ -144,34 +144,34 @@
|
||||
<buttons/>
|
||||
<keypad_buttons/>
|
||||
<analogs>
|
||||
<analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="Key 1 R" devid="Lights 01-14" index="0"/>
|
||||
@@ -277,9 +277,9 @@
|
||||
</buttons>
|
||||
<keypad_buttons/>
|
||||
<analogs>
|
||||
<analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
</analogs>
|
||||
<lights/>
|
||||
<!-- Gen 1 FAUCETWO in HID mode; should also work for later gens -->
|
||||
@@ -296,8 +296,8 @@
|
||||
</buttons>
|
||||
<keypad_buttons/>
|
||||
<analogs>
|
||||
<analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1"/>
|
||||
<analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="BT-A" devid="Lights (MI_02)" index="24"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
+10
-14
@@ -68,8 +68,8 @@ DIST_FOLDER="./dist"
|
||||
DIST_NAME="spice2x-$(date +%y)-$(date +%m)-$(date +%d).zip"
|
||||
DIST_NAME_EXTRAS="spice2x-$(date +%y)-$(date +%m)-$(date +%d)-full.zip"
|
||||
DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.'
|
||||
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm"
|
||||
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux"
|
||||
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm spicetools_sdk_sample_v0_flat_c_32"
|
||||
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux spicetools_sdk_sample_v0_flat_c_64 spicetools_sdk_sample_v0_cpp_64"
|
||||
TARGETS_XP32="spicetools_cfg spicetools_spice"
|
||||
TARGETS_XP64="spicetools_spice64"
|
||||
|
||||
@@ -269,6 +269,8 @@ rm -rf ${OUTDIR_EXTRAS}
|
||||
mkdir -p ${OUTDIR_EXTRAS}
|
||||
mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware
|
||||
mkdir -p ${OUTDIR_EXTRAS}/linux
|
||||
mkdir -p ${OUTDIR_EXTRAS}/sdk/samples/32
|
||||
mkdir -p ${OUTDIR_EXTRAS}/sdk/samples/64
|
||||
if ((BUILD_XP > 0))
|
||||
then
|
||||
mkdir -p ${OUTDIR_EXTRAS}/winxp
|
||||
@@ -281,16 +283,8 @@ then
|
||||
cp ${BUILDDIR_32}/spicetools/spicecfg-pdb.pdb ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice-pdb.exe ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice-pdb.pdb ${OUTDIR} 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/kbt.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/kld.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/spice64-pdb.exe ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/spice64-pdb.pdb ${OUTDIR} 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/kbt.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/kld.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/nvEncodeAPI64.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
else
|
||||
# release files
|
||||
cp ${BUILDDIR_32}/spicetools/spicecfg.exe ${OUTDIR} 2>/dev/null
|
||||
@@ -298,16 +292,15 @@ else
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice.exe ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice_laa.exe ${OUTDIR_EXTRAS}/largeaddressaware/spice.exe 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice_linux.exe ${OUTDIR_EXTRAS}/linux/spice.exe 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/kbt.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/kld.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/spice64.exe ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/spice64_linux.exe ${OUTDIR_EXTRAS}/linux/spice64.exe 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/kbt.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/kld.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/nvEncodeAPI64.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/sdk_sample_v0_flat_c.dll ${OUTDIR_EXTRAS}/sdk/samples/32/v0_flat_c.dll 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/sdk_sample_v0_flat_c.dll ${OUTDIR_EXTRAS}/sdk/samples/64/v0_flat_c.dll 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/sdk_sample_v0_cpp.dll ${OUTDIR_EXTRAS}/sdk/samples/64/v0_cpp.dll 2>/dev/null
|
||||
if ((BUILD_XP > 0))
|
||||
then
|
||||
cp ${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
|
||||
@@ -326,6 +319,9 @@ then
|
||||
echo "WARNING: Couldn't get git to create the archive. Is this a git repository?"
|
||||
fi
|
||||
|
||||
# sdk headers
|
||||
cp -r ./sdk/include ${OUTDIR_EXTRAS}/sdk
|
||||
|
||||
# copy resources
|
||||
rm -rf ${OUTDIR_EXTRAS}/api
|
||||
mkdir ${OUTDIR_EXTRAS}/api
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
docker build --pull external/docker -t spicetools/deps
|
||||
docker build --build-context gitroot=%cd%/../../.git . -t spicetools/spice
|
||||
docker run --rm -it -v %cd%/dist:/src/src/spice2x/dist -v %cd%/bin:/src/src/spice2x/bin -v %cd%/.ccache:/src/src/spice2x/.ccache spicetools/spice %*
|
||||
docker run --rm -i -v %cd%/dist:/src/src/spice2x/dist -v %cd%/bin:/src/src/spice2x/bin -v %cd%/.ccache:/src/src/spice2x/.ccache spicetools/spice %*
|
||||
@REM to generate PDBs, set DEBUG to 1 in build_all.sh, place cv2pdb in external\cv2pdb, and run below
|
||||
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spicecfg.exe bin\spice2x\spicecfg-pdb.exe bin\spice2x\spicecfg-pdb.pdb
|
||||
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spice.exe bin\spice2x\spice-pdb.exe bin\spice2x\spice-pdb.pdb
|
||||
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spice64.exe bin\spice2x\spice64-pdb.exe bin\spice2x\spice64-pdb.pdb
|
||||
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spice64.exe bin\spice2x\spice64-pdb.exe bin\spice2x\spice64-pdb.pdb
|
||||
|
||||
@@ -237,7 +237,8 @@ float Analog::applyDeadzone(float raw_value) {
|
||||
deadzone = -deadzone;
|
||||
}
|
||||
|
||||
if (getType() != GameAPI::Analogs::AnalogType::LinearPositive) {
|
||||
// relative mode assumes that user is using a stick, so center is neutral regardless of analog type
|
||||
if (getType() != GameAPI::Analogs::AnalogType::LinearPositive || isRelativeMode()) {
|
||||
|
||||
// calculate values
|
||||
const auto delta = value - 0.5f;
|
||||
@@ -290,4 +291,75 @@ float Analog::applyDeadzone(float raw_value) {
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
float Analog::getRelativeModeValue(float raw_value) {
|
||||
const auto now = get_performance_seconds();
|
||||
auto delta_time = now - this->rel_mode_last_read_time_s;
|
||||
|
||||
if (this->rel_mode_last_read_time_s != 0.f) {
|
||||
// some heuristics to prevent huge jumps:
|
||||
// * if we went for more than 250ms without polls, discard it (e.g., during loading screens)
|
||||
// * cap the delta at 100ms to prevent huge jumps in case of very infrequent polling
|
||||
if (delta_time < 0.f || 0.25f < delta_time) {
|
||||
delta_time = 0.f;
|
||||
} else if (delta_time > 0.1f) {
|
||||
delta_time = 0.1f;
|
||||
}
|
||||
|
||||
// scale [0, 1] to [-1, 1] to simplify calculations
|
||||
const auto delta_raw_value = (raw_value - 0.5f) * 2.f;
|
||||
|
||||
// target is one revolution per second at max speed at 1.0 sensitivity
|
||||
auto adjusted_delta_value = delta_raw_value * delta_time;
|
||||
|
||||
// multiplier / divisor
|
||||
if (this->getMultiplier() > 1) {
|
||||
adjusted_delta_value *= this->getMultiplier();
|
||||
} else if (this->getMultiplier() < -1) {
|
||||
adjusted_delta_value /= -this->getMultiplier();
|
||||
}
|
||||
|
||||
// sensitivity
|
||||
if (this->isSensitivitySet()) {
|
||||
adjusted_delta_value *= this->getSensitivity();
|
||||
}
|
||||
|
||||
// calculate the new absolute value
|
||||
this->rel_mode_absolute_value += adjusted_delta_value;
|
||||
}
|
||||
|
||||
// update for next poll
|
||||
this->rel_mode_last_read_time_s = now;
|
||||
this->rel_mode_absolute_value = normalizeAnalogValue(this->rel_mode_absolute_value);
|
||||
return this->rel_mode_absolute_value;
|
||||
}
|
||||
|
||||
float Analog::getDelayedValue(float raw_value) {
|
||||
const double delay_ms = static_cast<double>(this->getDelayMs());
|
||||
if (delay_ms == 0.0) {
|
||||
return raw_value;
|
||||
}
|
||||
|
||||
// always push a new value
|
||||
const auto now = get_performance_milliseconds();
|
||||
this->delayed_inputs.emplace(now, raw_value);
|
||||
|
||||
// drain the queue down to reasonable length to prevent unconstrained growth
|
||||
// this would accommodate 1 second at ~1000Hz which is overkill
|
||||
// (UI only allows for 250ms of delay)
|
||||
while (this->delayed_inputs.size() > 1024) {
|
||||
this->delayed_inputs.pop();
|
||||
}
|
||||
|
||||
// pop until we find the oldest value still inside the delay window
|
||||
while (this->delayed_inputs.size() > 1) {
|
||||
const auto delta_t = now - this->delayed_inputs.front().time_in_ms;
|
||||
if (delta_t <= delay_ms) {
|
||||
break;
|
||||
}
|
||||
this->delayed_inputs.pop();
|
||||
}
|
||||
|
||||
return this->delayed_inputs.front().value;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
@@ -34,6 +35,11 @@ struct AnalogMovingAverage {
|
||||
float cosine;
|
||||
};
|
||||
|
||||
struct AnalogDelayEntry {
|
||||
double time_in_ms;
|
||||
float value;
|
||||
};
|
||||
|
||||
class Analog {
|
||||
private:
|
||||
std::string name;
|
||||
@@ -63,6 +69,15 @@ private:
|
||||
float divisor_previous_value = 0.5f;
|
||||
unsigned short divisor_region = 0;
|
||||
|
||||
// relative input mode
|
||||
float rel_mode_absolute_value = 0.5f;
|
||||
float rel_mode_last_read_time_s = 0.f;
|
||||
bool relative_mode = false;
|
||||
|
||||
// delay
|
||||
uint32_t delay_ms = 0;
|
||||
std::queue<AnalogDelayEntry> delayed_inputs;
|
||||
|
||||
float calculateAngularDifference(float old_rads, float new_rads);
|
||||
float normalizeAngle(float rads);
|
||||
float normalizeAnalogValue(float value);
|
||||
@@ -83,6 +98,8 @@ public:
|
||||
float applyAngularSensitivity(float raw_rads);
|
||||
float applyMultiplier(float raw_value);
|
||||
float applyDeadzone(float raw_value);
|
||||
float getRelativeModeValue(float raw_value);
|
||||
float getDelayedValue(float raw_value);
|
||||
|
||||
inline bool isSet() {
|
||||
if (this->override_enabled) {
|
||||
@@ -98,7 +115,9 @@ public:
|
||||
smoothing = false;
|
||||
deadzone_mirror = false;
|
||||
setMultiplier(1);
|
||||
setRelativeMode(false);
|
||||
setLastState(0.5f);
|
||||
setDelayMs(0);
|
||||
}
|
||||
|
||||
inline void clearBindings() {
|
||||
@@ -195,6 +214,19 @@ public:
|
||||
this->last_state = last_state;
|
||||
}
|
||||
|
||||
inline bool isRelativeMode() const {
|
||||
return this->relative_mode;
|
||||
}
|
||||
|
||||
inline void setRelativeMode(bool relative_mode) {
|
||||
if (relative_mode) {
|
||||
this->smoothing = false;
|
||||
}
|
||||
this->relative_mode = relative_mode;
|
||||
this->rel_mode_absolute_value = 0.5f;
|
||||
this->rel_mode_last_read_time_s = 0.f;
|
||||
}
|
||||
|
||||
inline GameAPI::Analogs::AnalogType getType() const {
|
||||
return this->type;
|
||||
}
|
||||
@@ -202,4 +234,12 @@ public:
|
||||
inline void setType(GameAPI::Analogs::AnalogType type) {
|
||||
this->type = type;
|
||||
}
|
||||
|
||||
inline uint32_t getDelayMs() const {
|
||||
return this->delay_ms;
|
||||
}
|
||||
|
||||
inline void setDelayMs(uint32_t delay_ms) {
|
||||
this->delay_ms = delay_ms;
|
||||
}
|
||||
};
|
||||
|
||||
+34
-21
@@ -679,38 +679,45 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
|
||||
}
|
||||
|
||||
// deadzone
|
||||
// do not apply deadzone to circular analogs since it doesn't make sense
|
||||
if (analog.isDeadzoneSet() && analog.getType() != AnalogType::Circular) {
|
||||
// do not apply deadzone to circular analogs since it doesn't make sense (except in relative mode)
|
||||
if (analog.isDeadzoneSet() &&
|
||||
(analog.getType() != AnalogType::Circular || analog.isRelativeMode())) {
|
||||
value = analog.applyDeadzone(value);
|
||||
}
|
||||
|
||||
if (analog.getType() == AnalogType::Circular) {
|
||||
// integer multiplier
|
||||
value = analog.applyMultiplier(value);
|
||||
|
||||
// smoothing/sensitivity
|
||||
if (analog.getSmoothing() || analog.isSensitivitySet()) {
|
||||
float rads = value * (float) M_TAU;
|
||||
if (analog.isRelativeMode()) {
|
||||
value = analog.getRelativeModeValue(value);
|
||||
|
||||
// smoothing
|
||||
if (analog.getSmoothing()) {
|
||||
} else {
|
||||
// integer multiplier
|
||||
value = analog.applyMultiplier(value);
|
||||
|
||||
// preserve direction
|
||||
if (rads >= M_TAU) {
|
||||
rads -= 0.0001f;
|
||||
// smoothing/sensitivity
|
||||
if (analog.getSmoothing() || analog.isSensitivitySet()) {
|
||||
float rads = value * (float) M_TAU;
|
||||
|
||||
// smoothing
|
||||
if (analog.getSmoothing()) {
|
||||
|
||||
// preserve direction
|
||||
if (rads >= M_TAU) {
|
||||
rads -= 0.0001f;
|
||||
}
|
||||
|
||||
// calculate angle
|
||||
rads = analog.getSmoothedValue(rads);
|
||||
}
|
||||
|
||||
// calculate angle
|
||||
rads = analog.getSmoothedValue(rads);
|
||||
}
|
||||
// sensitivity
|
||||
if (analog.isSensitivitySet()) {
|
||||
rads = analog.applyAngularSensitivity(rads);
|
||||
}
|
||||
|
||||
// sensitivity
|
||||
if (analog.isSensitivitySet()) {
|
||||
rads = analog.applyAngularSensitivity(rads);
|
||||
// apply to value
|
||||
value = rads * (float) M_1_TAU;
|
||||
}
|
||||
|
||||
// apply to value
|
||||
value = rads * (float) M_1_TAU;
|
||||
}
|
||||
} else {
|
||||
// sensitivity
|
||||
@@ -760,6 +767,12 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
|
||||
value = std::clamp(value, 0.f, 1.f);
|
||||
}
|
||||
}
|
||||
|
||||
// delay
|
||||
if (analog.getDelayMs() > 0) {
|
||||
value = analog.getDelayedValue(value);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case rawinput::MIDI: {
|
||||
|
||||
@@ -252,6 +252,8 @@ bool Config::addGame(Game &game) {
|
||||
bool invert = false;
|
||||
bool smoothing = false;
|
||||
int multiplier = 1;
|
||||
bool relative_mode = false;
|
||||
uint32_t delay = 0;
|
||||
tinyxml2::XMLError err1 = gameAnalogNode->QueryIntAttribute("index", &index);
|
||||
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
|
||||
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
|
||||
@@ -259,6 +261,8 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->QueryBoolAttribute("invert", &invert);
|
||||
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
|
||||
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
|
||||
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
|
||||
gameAnalogNode->QueryUnsignedAttribute("delay_ms", &delay);
|
||||
const char *devid = gameAnalogNode->Attribute("devid");
|
||||
|
||||
if (err1 != tinyxml2::XMLError::XML_SUCCESS || !devid) {
|
||||
@@ -273,6 +277,8 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->SetAttribute("invert", it.getInvert());
|
||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
} else {
|
||||
it.setIndex(static_cast<unsigned short int>(index));
|
||||
@@ -283,6 +289,8 @@ bool Config::addGame(Game &game) {
|
||||
it.setInvert(invert);
|
||||
it.setSmoothing(smoothing);
|
||||
it.setMultiplier(multiplier);
|
||||
it.setRelativeMode(relative_mode);
|
||||
it.setDelayMs(delay);
|
||||
}
|
||||
} else {
|
||||
gameAnalogNode = this->configFile.NewElement("analog");
|
||||
@@ -294,6 +302,8 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->SetAttribute("invert", it.getInvert());
|
||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
|
||||
gameAnalogNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
}
|
||||
@@ -447,6 +457,8 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->SetAttribute("invert", it.getInvert());
|
||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
}
|
||||
|
||||
@@ -679,6 +691,8 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
|
||||
gameAnalogNode->SetAttribute("invert", analog.getInvert());
|
||||
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay_ms", analog.getDelayMs());
|
||||
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
|
||||
} else {
|
||||
gameAnalogNode = this->configFile.NewElement("analog");
|
||||
@@ -689,6 +703,8 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
|
||||
gameAnalogNode->SetAttribute("invert", analog.getInvert());
|
||||
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay_ms", analog.getDelayMs());
|
||||
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
}
|
||||
@@ -1125,6 +1141,8 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
||||
bool invert = false;
|
||||
bool smoothing = false;
|
||||
int multiplier = 1;
|
||||
bool relative_mode = false;
|
||||
uint32_t delay = 0;
|
||||
gameAnalogNode->QueryIntAttribute("index", &index);
|
||||
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
|
||||
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
|
||||
@@ -1132,6 +1150,8 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
||||
gameAnalogNode->QueryBoolAttribute("invert", &invert);
|
||||
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
|
||||
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
|
||||
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
|
||||
gameAnalogNode->QueryUnsignedAttribute("delay_ms", &delay);
|
||||
const char *devid = gameAnalogNode->Attribute("devid");
|
||||
|
||||
// create analog and add to list
|
||||
@@ -1143,6 +1163,8 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
||||
analog.setInvert(invert);
|
||||
analog.setSmoothing(smoothing);
|
||||
analog.setMultiplier(multiplier);
|
||||
analog.setRelativeMode(relative_mode);
|
||||
analog.setDelayMs(delay);
|
||||
if (devid) {
|
||||
analog.setDeviceIdentifier(devid);
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ namespace overlay::windows {
|
||||
el->SetAttribute("invert", analog.invert);
|
||||
el->SetAttribute("smoothing", analog.smoothing);
|
||||
el->SetAttribute("multiplier", analog.multiplier);
|
||||
el->SetAttribute("relative", analog.relative_mode);
|
||||
el->SetAttribute("delay_ms", analog.delay_ms);
|
||||
parent->InsertEndChild(el);
|
||||
}
|
||||
|
||||
@@ -90,6 +92,8 @@ namespace overlay::windows {
|
||||
el->QueryBoolAttribute("invert", &a.invert);
|
||||
el->QueryBoolAttribute("smoothing", &a.smoothing);
|
||||
el->QueryIntAttribute("multiplier", &a.multiplier);
|
||||
el->QueryBoolAttribute("relative", &a.relative_mode);
|
||||
el->QueryUnsignedAttribute("delay_ms", &a.delay_ms);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
Vendored
+1
@@ -20,6 +20,7 @@ Usage:
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <cstdint>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
Vendored
+1
@@ -39,6 +39,7 @@
|
||||
#define ROBIN_HOOD_VERSION_PATCH 2 // for backwards-compatible bug fixes
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "util/logging.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "games/io.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
#include "io.h"
|
||||
|
||||
@@ -112,7 +113,7 @@ namespace games::ccj {
|
||||
static std::chrono::time_point<std::chrono::steady_clock> lastModified = std::chrono::steady_clock::now();
|
||||
static std::chrono::milliseconds debounceDuration(100);
|
||||
auto currentTime = std::chrono::steady_clock::now();
|
||||
bool pressed = get_async_secondary_mouse() & 0x8000;
|
||||
bool pressed = get_async_secondary_mouse();
|
||||
bool focused = GetForegroundWindow() == hWnd;
|
||||
|
||||
if (focused && MOUSE_TRACKBALL_USE_TOGGLE && pressed && (currentTime - lastModified > debounceDuration)) {
|
||||
@@ -243,6 +244,7 @@ namespace games::ccj {
|
||||
log_info("trackball", "thread start, use mouse: {}, toggle: {}", MOUSE_TRACKBALL, MOUSE_TRACKBALL_USE_TOGGLE);
|
||||
|
||||
tbThread = new std::thread([&] {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
while (tbThreadRunning) {
|
||||
if (hWnd && wndProc) {
|
||||
wndProc(hWnd, WM_INPUT, RIM_INPUT, (LPARAM)fakeHandle);
|
||||
@@ -251,7 +253,7 @@ namespace games::ccj {
|
||||
if (!tbThreadRunning)
|
||||
break;
|
||||
|
||||
std::this_thread::sleep_for(10ms);
|
||||
timer.sleep(10);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "cfg/api.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "../ddr.h"
|
||||
@@ -459,7 +460,8 @@ int games::ddr::DDRP3IOHandle::device_io(
|
||||
if (nOutBufferSize >= 4) {
|
||||
|
||||
// cool down
|
||||
Sleep(1);
|
||||
static thread_local timeutils::PreciseSleepTimer timer;
|
||||
timer.sleep(1);
|
||||
|
||||
// get controls as single variable (4 bytes)
|
||||
auto &controls = *(uint32_t*) lpOutBuffer;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "../io.h"
|
||||
@@ -255,7 +256,8 @@ namespace games::ddr {
|
||||
case P4IO_IOCTL_GET_INPUTS: {
|
||||
|
||||
// Prevents this function from being called at its normal 2000-2500 kHz cadence and overloading the CPU, instead reduces it to 250 Hz
|
||||
Sleep(4);
|
||||
static thread_local timeutils::PreciseSleepTimer timer;
|
||||
timer.sleep(4);
|
||||
|
||||
memset(lpOutBuffer, 0, 16);
|
||||
auto controls = (uint32_t*) lpOutBuffer;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "games/game.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/memutils.h"
|
||||
#include "rgb_cam.h"
|
||||
|
||||
@@ -221,10 +222,11 @@ namespace games::drs {
|
||||
void start_touch() {
|
||||
std::thread t([] {
|
||||
log_info("drs", "starting touch input thread");
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// main loop
|
||||
while (TRUE) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
timer.sleep(1);
|
||||
|
||||
TOUCH_EVENTS.clear();
|
||||
touch_get_events(TOUCH_EVENTS);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "misc/eamuse.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "iidx.h"
|
||||
@@ -82,7 +83,8 @@ bool games::iidx::IIDXFMSerialHandle::FMSerialDevice::parse_msg(
|
||||
}
|
||||
|
||||
// sleep - otherwise the IO thread will go too hard on the CPU
|
||||
Sleep(1);
|
||||
static thread_local timeutils::PreciseSleepTimer timer;
|
||||
timer.sleep(1);
|
||||
|
||||
// generate message
|
||||
auto msg = this->create_msg(msg_in, 0x2E);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "misc/eamuse.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "iidx.h"
|
||||
@@ -82,7 +83,8 @@ bool games::iidx::BI2XSerialHandle::BI2XDevice::parse_msg(
|
||||
}
|
||||
|
||||
// sleep - otherwise the IO thread will go too hard on the CPU
|
||||
Sleep(1);
|
||||
static thread_local timeutils::PreciseSleepTimer timer;
|
||||
timer.sleep(1);
|
||||
|
||||
// generate message
|
||||
auto msg = this->create_msg(msg_in, 0x2E);
|
||||
|
||||
@@ -783,42 +783,7 @@ namespace games::iidx {
|
||||
}
|
||||
|
||||
// return higher 8 bit
|
||||
uint8_t result = (uint8_t) (ret_value >> 2);
|
||||
|
||||
// delay
|
||||
if ((player == 0 && TT_DELAY_P1 > 0) ||
|
||||
(player == 1 && TT_DELAY_P2 > 0)) {
|
||||
|
||||
static std::queue<std::pair<double, uint8_t>> delay_queue[2];
|
||||
auto &queue = delay_queue[player];
|
||||
|
||||
const auto max_delta_ms =
|
||||
static_cast<double>((player == 0) ? TT_DELAY_P1 : TT_DELAY_P2);
|
||||
|
||||
// always push a new value
|
||||
const auto now = get_performance_milliseconds();
|
||||
queue.push(std::make_pair(now, result));
|
||||
|
||||
// drain the queue down to reasonable length to prevent unconstrained growth
|
||||
// this would accommodate 1 second at ~1000Hz
|
||||
// (in reality all three iidx I/O emulation runs well under 500Hz)
|
||||
while (queue.size() > 1024) {
|
||||
queue.pop();
|
||||
}
|
||||
|
||||
// pop until we find one that falls just under the time threshold
|
||||
while (!queue.empty()) {
|
||||
const auto delta_t = now - queue.front().first;
|
||||
if (delta_t <= max_delta_ms) {
|
||||
break;
|
||||
}
|
||||
queue.pop();
|
||||
}
|
||||
|
||||
result = queue.front().second;
|
||||
}
|
||||
|
||||
return result;
|
||||
return (uint8_t)(ret_value >> 2);
|
||||
}
|
||||
|
||||
unsigned char get_slider(uint8_t slider) {
|
||||
|
||||
@@ -31,8 +31,6 @@ namespace games::iidx {
|
||||
extern bool NATIVE_TOUCH;
|
||||
extern std::optional<std::string> SOUND_OUTPUT_DEVICE;
|
||||
extern std::optional<std::string> ASIO_DRIVER;
|
||||
extern uint32_t TT_DELAY_P1;
|
||||
extern uint32_t TT_DELAY_P2;
|
||||
extern uint8_t DIGITAL_TT_SENS;
|
||||
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||
extern std::optional<std::string> SCREEN_MODE;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
#include "mf_wrappers.h"
|
||||
|
||||
@@ -399,6 +400,7 @@ namespace games::iidx {
|
||||
void IIDXLocalCamera::CreateThread() {
|
||||
// Create thread
|
||||
m_drawThread = new std::thread([this]() {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||
|
||||
double accumulator = 0.0;
|
||||
@@ -412,7 +414,7 @@ namespace games::iidx {
|
||||
accumulator -= 1.0;
|
||||
floorFrameTimeMicroSec += 1;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(floorFrameTimeMicroSec));
|
||||
timer.sleep(std::chrono::microseconds(floorFrameTimeMicroSec));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "touch/touch.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
#define POKE_NATIVE_TOUCH 0
|
||||
|
||||
@@ -200,6 +201,7 @@ namespace games::iidx::poke {
|
||||
// create new thread
|
||||
THREAD_RUNNING = true;
|
||||
THREAD = new std::thread([] {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// log
|
||||
log_info("poke", "enabled");
|
||||
@@ -319,7 +321,7 @@ namespace games::iidx::poke {
|
||||
}
|
||||
|
||||
// slow down
|
||||
Sleep(50);
|
||||
timer.sleep(50);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
||||
+84
-11
@@ -19,7 +19,7 @@
|
||||
namespace games::jb {
|
||||
|
||||
// touch stuff
|
||||
bool TOUCH_LEGACY_BOX = false;
|
||||
JubeatTouchAlgorithm TOUCH_ALGORITHM = Improved;
|
||||
static bool TOUCH_ENABLE = false;
|
||||
static bool TOUCH_ATTACHED = false;
|
||||
static bool IS_PORTRAIT = true;
|
||||
@@ -78,7 +78,7 @@ namespace games::jb {
|
||||
// note that the IO code in device.cpp will correctly compensate for orientation, depending on the model.
|
||||
TOUCH_POINTS.clear();
|
||||
touch_get_points(TOUCH_POINTS);
|
||||
if (TOUCH_LEGACY_BOX) {
|
||||
if (TOUCH_ALGORITHM == Legacy) {
|
||||
auto offset = IS_PORTRAIT ? 580 : 0;
|
||||
for (auto &tp : TOUCH_POINTS) {
|
||||
|
||||
@@ -94,6 +94,17 @@ namespace games::jb {
|
||||
}
|
||||
} else {
|
||||
for (auto &tp : TOUCH_POINTS) {
|
||||
// check window out of bounds
|
||||
if (IS_PORTRAIT) {
|
||||
if (tp.x > 768 || tp.y > 1360) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (tp.x > 1360 || tp.y > 768) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int x_relative = tp.x;
|
||||
int y_relative = tp.y;
|
||||
|
||||
@@ -110,19 +121,66 @@ namespace games::jb {
|
||||
y_relative -= 8;
|
||||
}
|
||||
|
||||
if (x_relative < 0 || y_relative < 0) {
|
||||
int x_index = -1;
|
||||
int x_hitbox = 0;
|
||||
int y_index = -1;
|
||||
int y_hitbox = 0;
|
||||
|
||||
// x_hitbox and y_hitbox is relative to top-left pixel of each button
|
||||
if (x_relative >= 0) {
|
||||
x_index = x_relative / JB_BUTTON_HITBOX;
|
||||
x_hitbox = x_relative % JB_BUTTON_HITBOX;
|
||||
}
|
||||
if (y_relative >= 0) {
|
||||
y_index = y_relative / JB_BUTTON_HITBOX;
|
||||
y_hitbox = y_relative % JB_BUTTON_HITBOX;
|
||||
}
|
||||
|
||||
// log_info("jb", "raw={}, idx={}, hitbox={}", tp.x, x_index, x_hitbox);
|
||||
|
||||
if (TOUCH_ALGORITHM == Improved) {
|
||||
if (IS_PORTRAIT) {
|
||||
// extend to left border
|
||||
if (x_relative < 0) {
|
||||
x_index = 0;
|
||||
}
|
||||
// right and bottom borders are covered by the hit box
|
||||
} else {
|
||||
// extend to top border
|
||||
if (y_relative < 0) {
|
||||
y_index = 0;
|
||||
}
|
||||
// extend to left border
|
||||
if (x_relative < 0) {
|
||||
x_index = 0;
|
||||
}
|
||||
// bottom border is covered by the hit box
|
||||
// rightmost edge - ignore them entirely
|
||||
if (x_index == 3 && JB_BUTTON_SIZE < x_hitbox) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (x_index < 0 || y_index < 0 || x_index > 3 || y_index > 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// x_hitbox and y_hitbox is relative to top-left pixel of each button
|
||||
int x_index = x_relative / JB_BUTTON_HITBOX;
|
||||
int x_hitbox = x_relative % JB_BUTTON_HITBOX;
|
||||
int y_index = y_relative / JB_BUTTON_HITBOX;
|
||||
int y_hitbox = y_relative % JB_BUTTON_HITBOX;
|
||||
|
||||
// check if the gap was touched
|
||||
if (x_hitbox > JB_BUTTON_SIZE || y_hitbox > JB_BUTTON_SIZE) {
|
||||
continue;
|
||||
if (TOUCH_ALGORITHM == AcAccurate) {
|
||||
// in ac-accurate mode, touching the gap is ignored
|
||||
if (x_hitbox > JB_BUTTON_SIZE || y_hitbox > JB_BUTTON_SIZE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
} else if (TOUCH_ALGORITHM == Improved) {
|
||||
// in improved mode, touching the gap triggers the closest button
|
||||
if (x_index <= 2 && (JB_BUTTON_SIZE + JB_BUTTON_GAP / 2) < x_hitbox) {
|
||||
x_index++;
|
||||
}
|
||||
if (y_index <= 2 && (JB_BUTTON_SIZE + JB_BUTTON_GAP / 2) < y_hitbox) {
|
||||
y_index++;
|
||||
}
|
||||
}
|
||||
|
||||
// set the corresponding state
|
||||
@@ -193,6 +251,21 @@ namespace games::jb {
|
||||
// enable touch
|
||||
TOUCH_ENABLE = true;
|
||||
|
||||
switch (TOUCH_ALGORITHM) {
|
||||
case Legacy:
|
||||
log_info("jubeat", "using 'legacy' touch targets");
|
||||
break;
|
||||
case Improved:
|
||||
log_info("jubeat", "using 'improved' touch targets");
|
||||
break;
|
||||
case AcAccurate:
|
||||
log_info("jubeat", "using 'ac accurate' touch targets");
|
||||
break;
|
||||
default:
|
||||
log_fatal("jubeat", "unknown touch algo detected in touch_update, this is a bug");
|
||||
break;
|
||||
}
|
||||
|
||||
// enable debug logging of gftools
|
||||
HMODULE gftools = libutils::try_module("gftools.dll");
|
||||
detour::inline_hook((void *) GFDbgSetReportFunc, libutils::try_proc(
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
|
||||
namespace games::jb {
|
||||
|
||||
enum JubeatTouchAlgorithm {
|
||||
Legacy,
|
||||
Improved,
|
||||
AcAccurate
|
||||
};
|
||||
|
||||
// touch stuff
|
||||
extern bool TOUCH_LEGACY_BOX;
|
||||
extern JubeatTouchAlgorithm TOUCH_ALGORITHM;
|
||||
extern bool TOUCH_STATE[16];
|
||||
void touch_update();
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "misc/eamuse.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
namespace games::nost::poke {
|
||||
|
||||
@@ -63,6 +64,7 @@ namespace games::nost::poke {
|
||||
// create new thread
|
||||
THREAD_RUNNING = true;
|
||||
THREAD = new std::thread([] {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
const DWORD touch_id = (DWORD)(0xFFFFFFFE);
|
||||
|
||||
const int swipe_anim_total_frames = 6;
|
||||
@@ -178,7 +180,7 @@ namespace games::nost::poke {
|
||||
}
|
||||
|
||||
// slow down
|
||||
Sleep(30);
|
||||
timer.sleep(30);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "touchpanel.h"
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "touch/touch.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
@@ -44,6 +45,9 @@ bool games::onpara::TouchPanelHandle::open(LPCWSTR lpFileName) {
|
||||
}
|
||||
|
||||
int games::onpara::TouchPanelHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
|
||||
|
||||
static thread_local timeutils::PreciseSleepTimer timer;
|
||||
|
||||
DWORD i;
|
||||
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
|
||||
|
||||
@@ -67,7 +71,7 @@ int games::onpara::TouchPanelHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesT
|
||||
enqueue_packet(report);
|
||||
|
||||
// prevent cpu bullying
|
||||
std::this_thread::sleep_for(1ms);
|
||||
timer.sleep(1);
|
||||
}
|
||||
|
||||
// copy from output queue
|
||||
@@ -133,4 +137,4 @@ bool games::onpara::TouchPanelHandle::close() {
|
||||
log_info("touchpanel", "Closed COM1 (Touch Panel)");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
+27
-13
@@ -4,29 +4,34 @@
|
||||
#include "hooks/devicehook.h"
|
||||
#include "hooks/audio/backends/dsound/dsound_backend.h"
|
||||
#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
|
||||
*/
|
||||
// increase touch poll rate
|
||||
if (dwMilliseconds == 8) {
|
||||
static bool initialized = false;
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
dwMilliseconds = 1000 / games::rb::TOUCH_POLL_RATE;
|
||||
}
|
||||
|
||||
// if we only sleep for 1ms we also don't need the high priority RB sets
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||
}
|
||||
dwMilliseconds = 1;
|
||||
// most calls from rb are actually non-alertable
|
||||
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
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "hooks/audio/audio.h"
|
||||
#include "hooks/audio/backends/wasapi/audio_client.h"
|
||||
#include "hooks/audio/backends/wasapi/defs.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
static REFERENCE_TIME WASAPI_TARGET_REFTIME = TARGET_REFTIME;
|
||||
|
||||
@@ -185,6 +186,7 @@ HRESULT WaveOutBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppDa
|
||||
}
|
||||
HRESULT WaveOutBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) {
|
||||
bool written = false;
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// reset the dispatcher event
|
||||
ResetEvent(this->dispatcher_event);
|
||||
@@ -209,7 +211,7 @@ HRESULT WaveOutBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwF
|
||||
|
||||
// avoid pegging the CPU
|
||||
if (!written) {
|
||||
Sleep(1);
|
||||
timer.sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -61,6 +61,7 @@ static std::condition_variable GRAPHICS_CAPTURE_CV[GRAPHICS_CAPTURE_SCREEN_NO] {
|
||||
|
||||
static std::optional<graphics_orientation> target_orientation_on_boot;
|
||||
static UINT target_refresh_rate_on_boot = 0;
|
||||
static std::optional<std::pair<uint32_t, uint32_t>> target_resolution_on_boot;
|
||||
static bool monitor_settings_changed = false;
|
||||
static bool monitor_layout_needs_reset = false;
|
||||
|
||||
@@ -163,6 +164,7 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
|
||||
if (wintouchemu::INJECT_MOUSE_AS_WM_TOUCH) {
|
||||
// drop mouse inputs since only wintouches should be used
|
||||
switch (uMsg) {
|
||||
case WM_MOUSEMOVE:
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
case WM_MBUTTONDOWN:
|
||||
@@ -355,16 +357,24 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
bool is_popn_sub_window = avs::game::is_model("M39") && window_name.ends_with("Sub Screen");
|
||||
bool is_gfdm_sub_window = games::gitadora::is_arena_model() && window_name.ends_with("SMALL");
|
||||
|
||||
// hide maximize button (prevent misaligned touches)
|
||||
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window || is_popn_sub_window) {
|
||||
// update style / ex-style
|
||||
if (is_tdj_sub_window || is_sdvx_sub_window || is_gfdm_sub_window || is_popn_sub_window) {
|
||||
// hide maximize button (prevent misaligned touches)
|
||||
dwStyle &= ~(WS_MAXIMIZEBOX);
|
||||
}
|
||||
// mouse clicks become misaligned when resized
|
||||
if (is_gfdm_sub_window) {
|
||||
|
||||
// mouse clicks become misaligned when resized
|
||||
dwStyle &= ~(WS_SIZEBOX);
|
||||
}
|
||||
if ((is_tdj_sub_window || is_sdvx_sub_window || is_popn_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
|
||||
dwStyle &= ~(WS_OVERLAPPEDWINDOW);
|
||||
|
||||
// borderless
|
||||
if (GRAPHICS_WINDOWED && GRAPHICS_WSUB_BORDERLESS) {
|
||||
dwStyle &= ~(WS_OVERLAPPEDWINDOW);
|
||||
}
|
||||
|
||||
// don't show the sub window on task bar / alt-tab targets
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
dwExStyle &= ~(WS_EX_APPWINDOW);
|
||||
dwExStyle |= WS_EX_TOOLWINDOW;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_sdvx_sub_window) {
|
||||
@@ -411,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);
|
||||
}
|
||||
@@ -692,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;
|
||||
@@ -1278,8 +1288,13 @@ void change_primary_monitor(const std::string &monitor_name) {
|
||||
Sleep(2000);
|
||||
}
|
||||
|
||||
void update_monitor(bool is_boot, std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate) {
|
||||
// note: all of this is only being done for the primary motnior
|
||||
void update_monitor(
|
||||
bool is_boot,
|
||||
std::optional<graphics_orientation> target_orientation,
|
||||
UINT target_refresh_rate,
|
||||
std::optional<std::pair<uint32_t, uint32_t>> target_resolution) {
|
||||
|
||||
// note: all of this is only being done for the primary monitor
|
||||
|
||||
// get current settings
|
||||
DEVMODEA dm = {};
|
||||
@@ -1357,6 +1372,22 @@ void update_monitor(bool is_boot, std::optional<graphics_orientation> target_ori
|
||||
needs_update = true;
|
||||
}
|
||||
|
||||
if (target_resolution.has_value()) {
|
||||
if (dm.dmPelsWidth != target_resolution.value().first ||
|
||||
dm.dmPelsHeight != target_resolution.value().second) {
|
||||
|
||||
log_misc(
|
||||
"graphics",
|
||||
"current resolution {}, {} => desired resolution {}, {}",
|
||||
dm.dmPelsWidth, dm.dmPelsHeight,
|
||||
target_resolution.value().first, target_resolution.value().second);
|
||||
|
||||
dm.dmPelsWidth = target_resolution.value().first;
|
||||
dm.dmPelsHeight = target_resolution.value().second;
|
||||
needs_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!needs_update) {
|
||||
// nothing to do
|
||||
log_misc("graphics", "display settings are already up to date, no changes needed");
|
||||
@@ -1393,17 +1424,30 @@ void update_monitor(bool is_boot, std::optional<graphics_orientation> target_ori
|
||||
}
|
||||
}
|
||||
|
||||
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate) {
|
||||
void update_monitor_on_boot(
|
||||
std::optional<graphics_orientation> target_orientation,
|
||||
UINT target_refresh_rate,
|
||||
std::optional<std::pair<uint32_t, uint32_t>> target_resolution) {
|
||||
|
||||
target_orientation_on_boot = target_orientation;
|
||||
target_refresh_rate_on_boot = target_refresh_rate;
|
||||
target_resolution_on_boot = target_resolution;
|
||||
log_misc("graphics", "applying monitor updates at boot...");
|
||||
update_monitor(true, target_orientation, target_refresh_rate);
|
||||
update_monitor(
|
||||
true,
|
||||
target_orientation,
|
||||
target_refresh_rate,
|
||||
target_resolution);
|
||||
}
|
||||
|
||||
void update_monitor_at_runtime() {
|
||||
if (monitor_settings_changed) {
|
||||
if (!GRAPHICS_WINDOWED && monitor_settings_changed) {
|
||||
log_misc("graphics", "applying monitor updates at runtime as window regained focus...");
|
||||
update_monitor(false, target_orientation_on_boot, target_refresh_rate_on_boot);
|
||||
update_monitor(
|
||||
false,
|
||||
target_orientation_on_boot,
|
||||
target_refresh_rate_on_boot,
|
||||
target_resolution_on_boot);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,10 @@ void graphics_load_windowed_subscreen_parameters();
|
||||
void graphics_window_check_bounds_before_creation(int &x, int &y, const int width, const int height);
|
||||
|
||||
void change_primary_monitor(const std::string &monitor_name);
|
||||
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate);
|
||||
void update_monitor_on_boot(
|
||||
std::optional<graphics_orientation> target_orientation,
|
||||
UINT target_refresh_rate,
|
||||
std::optional<std::pair<uint32_t, uint32_t>> target_resolution);
|
||||
|
||||
void update_monitor_at_runtime();
|
||||
void reset_monitor_on_exit();
|
||||
@@ -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"
|
||||
@@ -98,6 +100,7 @@
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "rawinput/touch.h"
|
||||
#include "reader/reader.h"
|
||||
#include "sdk/sdk.h"
|
||||
#include "stubs/stubs.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/cpuutils.h"
|
||||
@@ -107,6 +110,7 @@
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/peb.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/sysutils.h"
|
||||
#include "util/tapeled.h"
|
||||
@@ -242,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;
|
||||
@@ -297,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()) {
|
||||
@@ -383,6 +408,16 @@ int main_implementation(int argc, char *argv[]) {
|
||||
monitor_orientation = ORIENTATION_CW;
|
||||
}
|
||||
|
||||
std::optional<std::pair<uint32_t, uint32_t>> monitor_resolution;
|
||||
if (options[launcher::Options::ChangeResolution].is_active()) {
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(options[launcher::Options::ChangeResolution].value_text(), result)) {
|
||||
monitor_resolution = result;
|
||||
} else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_fatal("launcher", "failed to parse -changeres");
|
||||
}
|
||||
}
|
||||
|
||||
if (options[launcher::Options::spice2x_NoD3D9DeviceHook].value_bool()) {
|
||||
D3D9_DEVICE_HOOK_DISABLE = true;
|
||||
// touch emulation gets disabled, might as well turn these on
|
||||
@@ -518,12 +553,6 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::IIDXTDJMode].value_bool()) {
|
||||
games::iidx::TDJ_MODE = true;
|
||||
}
|
||||
if (options[launcher::Options::IIDXTTDelayP1].is_active()) {
|
||||
games::iidx::TT_DELAY_P1 = options[launcher::Options::IIDXTTDelayP1].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::IIDXTTDelayP2].is_active()) {
|
||||
games::iidx::TT_DELAY_P2 = options[launcher::Options::IIDXTTDelayP2].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].is_active()) {
|
||||
games::iidx::DIGITAL_TT_SENS = (uint8_t)
|
||||
options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].value_uint32();
|
||||
@@ -615,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();
|
||||
@@ -726,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;
|
||||
}
|
||||
@@ -883,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;
|
||||
@@ -1152,11 +1193,32 @@ int main_implementation(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
if (options[launcher::Options::spice2x_JubeatLegacyTouch].value_bool()) {
|
||||
games::jb::TOUCH_LEGACY_BOX = true;
|
||||
games::jb::TOUCH_ALGORITHM = games::jb::JubeatTouchAlgorithm::Legacy;
|
||||
}
|
||||
if (options[launcher::Options::JubeatTouchAlgo].is_active()) {
|
||||
if (options[launcher::Options::JubeatTouchAlgo].value_text() == "accurate") {
|
||||
games::jb::TOUCH_ALGORITHM = games::jb::JubeatTouchAlgorithm::AcAccurate;
|
||||
} else if (options[launcher::Options::JubeatTouchAlgo].value_text() == "legacy") {
|
||||
games::jb::TOUCH_ALGORITHM = games::jb::JubeatTouchAlgorithm::Legacy;
|
||||
} else {
|
||||
games::jb::TOUCH_ALGORITHM = games::jb::JubeatTouchAlgorithm::Improved;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -1278,6 +1340,10 @@ int main_implementation(int argc, char *argv[]) {
|
||||
games::loveplus::CAMERA_ENABLE = true;
|
||||
}
|
||||
|
||||
if (options[launcher::Options::DisableHighResTimer].value_bool()) {
|
||||
timeutils::TIMER_HACKS_DISABLE = true;
|
||||
}
|
||||
|
||||
// API debugging
|
||||
if (api_debug && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
API_CONTROLLER = std::make_unique<api::Controller>(api_port, api_pass, api_pretty);
|
||||
@@ -1537,16 +1603,18 @@ int main_implementation(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// early hooks
|
||||
for (auto &hook : early_hooks) {
|
||||
log_info("launcher", "loading early hook DLL {}", hook);
|
||||
libutils::print_dll_info(hook);
|
||||
HMODULE module;
|
||||
if (!(module = libutils::try_library(hook))) {
|
||||
log_warning("launcher", "failed to load early hook {}: {}", hook, get_last_error_string());
|
||||
deferredlogs::defer_error_messages({
|
||||
fmt::format("failed to load early hook {}:", hook),
|
||||
fmt::format(" {}", get_last_error_string())
|
||||
});
|
||||
if (!cfg_run) {
|
||||
for (auto &hook : early_hooks) {
|
||||
log_info("launcher", "loading early hook DLL {}", hook);
|
||||
libutils::print_dll_info(hook);
|
||||
HMODULE module;
|
||||
if (!(module = libutils::try_library(hook))) {
|
||||
log_warning("launcher", "failed to load early hook {}: {}", hook, get_last_error_string());
|
||||
deferredlogs::defer_error_messages({
|
||||
fmt::format("failed to load early hook {}:", hook),
|
||||
fmt::format(" {}", get_last_error_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2177,7 +2245,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::PrimaryMonitor].is_active()) {
|
||||
change_primary_monitor(options[launcher::Options::PrimaryMonitor].value_text());
|
||||
}
|
||||
update_monitor_on_boot(monitor_orientation, GRAPHICS_FORCE_REFRESH);
|
||||
update_monitor_on_boot(monitor_orientation, GRAPHICS_FORCE_REFRESH, monitor_resolution);
|
||||
|
||||
// initialize raw input
|
||||
RI_MGR = std::make_unique<rawinput::RawInputManager>();
|
||||
@@ -2230,10 +2298,18 @@ int main_implementation(int argc, char *argv[]) {
|
||||
hooks::lang::early_init();
|
||||
}
|
||||
|
||||
// use high resolution timer for this process
|
||||
timeutils::set_timer_resolution();
|
||||
|
||||
// load DLLs
|
||||
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();
|
||||
@@ -2364,6 +2440,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
});
|
||||
} else {
|
||||
bt5api_hook(module);
|
||||
sdk::register_sdk_hooks(hook, module);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2398,6 +2475,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
bt5api_init();
|
||||
}
|
||||
|
||||
// init SDK
|
||||
sdk::init_sdk_modules();
|
||||
|
||||
// API
|
||||
if (api_enable || std::min(api_serial_port.size(), api_serial_baud.size()) > 0) {
|
||||
API_CONTROLLER = std::make_unique<api::Controller>(api_port, api_pass, api_pretty);
|
||||
@@ -2505,6 +2585,8 @@ int main_implementation(int argc, char *argv[]) {
|
||||
bt5api_dispose();
|
||||
}
|
||||
|
||||
sdk::fini_sdk_modules();
|
||||
|
||||
// stop raw input
|
||||
RI_MGR.reset();
|
||||
|
||||
|
||||
@@ -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)",
|
||||
@@ -655,30 +655,6 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options",
|
||||
},
|
||||
{
|
||||
// IIDXTTDelayP1
|
||||
.title = "IIDX TT Delay ms (Player 1)",
|
||||
.name = "iidxttdelayp1",
|
||||
.desc = "Delays turntable by number of milliseconds. "
|
||||
"Delay will be *at most* the specified period; maximum error is dependent on the game's poll rate. "
|
||||
"As usual, changing any option requires a restart. Default: 0 (no delay).",
|
||||
.type = OptionType::Integer,
|
||||
.setting_name = "(0-500)",
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
{
|
||||
// IIDXTTDelayP2
|
||||
.title = "IIDX TT Delay ms (Player 2)",
|
||||
.name = "iidxttdelayp2",
|
||||
.desc = "Delays turntable by number of milliseconds. "
|
||||
"Delay will be *at most* the specified period; maximum error is dependent on the game's poll rate. "
|
||||
"As usual, changing any option requires a restart. Default: 0 (no delay).",
|
||||
.type = OptionType::Integer,
|
||||
.setting_name = "(0-500)",
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
{
|
||||
// spice2x_IIDXDigitalTTSensitivity
|
||||
.title = "IIDX Digital TT Sensitivity",
|
||||
@@ -1062,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",
|
||||
@@ -1080,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",
|
||||
@@ -1093,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,
|
||||
@@ -1292,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)",
|
||||
@@ -1300,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)",
|
||||
@@ -1308,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)",
|
||||
@@ -1316,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)",
|
||||
@@ -1325,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)",
|
||||
@@ -1334,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)",
|
||||
@@ -1343,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)",
|
||||
@@ -1352,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)",
|
||||
@@ -1361,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)",
|
||||
@@ -1485,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)",
|
||||
},
|
||||
@@ -1608,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",
|
||||
},
|
||||
@@ -1712,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,
|
||||
@@ -1743,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",
|
||||
@@ -1754,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 = {
|
||||
@@ -1831,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)",
|
||||
},
|
||||
@@ -1843,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)",
|
||||
},
|
||||
@@ -1944,6 +1922,15 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
{"3", "Landscape, Flipped"}
|
||||
},
|
||||
},
|
||||
{
|
||||
// ChangeResolution
|
||||
.title = "Change Monitor Resolution",
|
||||
.name = "changeres",
|
||||
.desc = "Changes monitor resolution before booting the game.",
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "1280,720",
|
||||
.category = "Monitor"
|
||||
},
|
||||
{
|
||||
.title = "AVS Log Level",
|
||||
.name = "loglevel",
|
||||
@@ -1976,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",
|
||||
},
|
||||
@@ -2303,19 +2290,35 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
},
|
||||
{
|
||||
// spice2x_JubeatLegacyTouch
|
||||
.title = "JB Legacy Touch Targets",
|
||||
.title = "JB Legacy Touch Targets (Deprecated - use -jubeattouchalgo instead)",
|
||||
.name = "sp2x-jubeatlegacytouch",
|
||||
.display_name = "jubeatlegacytouch",
|
||||
.aliases= "jubeatlegacytouch",
|
||||
.desc = "For touch screen players - use the legacy & less accurate grid-based layout for touch recognition, "
|
||||
"instead of the new & more accurate touch targets. Default: off.",
|
||||
.desc = "Deprecated - use -jubeattouchalgo instead.",
|
||||
.type = OptionType::Bool,
|
||||
.hidden = true,
|
||||
.game_name = "Jubeat",
|
||||
.category = "Game Options",
|
||||
},
|
||||
{
|
||||
.title = "JB Touch Algorithm",
|
||||
.name = "jubeattouchalgo",
|
||||
.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)",
|
||||
.type = OptionType::Enum,
|
||||
.game_name = "Jubeat",
|
||||
.category = "Game Options",
|
||||
.elements = {
|
||||
{"legacy", ""},
|
||||
{"improved", ""},
|
||||
{"accurate", ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
// spice2x_RBTouchScale
|
||||
.title = "RB Scale Touch Input",
|
||||
.title = "RB Touch Emulation Scale",
|
||||
.name = "sp2x-rbscaletouch",
|
||||
.display_name = "rbscaletouch",
|
||||
.aliases= "rbscaletouch",
|
||||
@@ -2325,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",
|
||||
@@ -2462,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",
|
||||
@@ -2501,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)",
|
||||
},
|
||||
@@ -2549,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",
|
||||
@@ -2560,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"
|
||||
@@ -2664,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",
|
||||
@@ -2793,7 +2843,42 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Otoca D'or",
|
||||
.category = "Game Options (Peripherals)",
|
||||
}
|
||||
},
|
||||
{
|
||||
// DisableHighResTimer
|
||||
.title = "Use Legacy Timers",
|
||||
.name = "notimerhacks",
|
||||
.desc = "Disables high resolution timers, reverting to legacy behavior. "
|
||||
"Recommended that you leave this OFF for optimal input latency, "
|
||||
"unless you're on a resource-constrained system (e.g., old cabinet PC)",
|
||||
.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) {
|
||||
|
||||
@@ -69,8 +69,6 @@ namespace launcher {
|
||||
IIDXAsioDriver,
|
||||
IIDXBIO2FW,
|
||||
IIDXTDJMode,
|
||||
IIDXTTDelayP1,
|
||||
IIDXTTDelayP2,
|
||||
spice2x_IIDXDigitalTTSensitivity,
|
||||
IIDXDigitalTTSocd,
|
||||
spice2x_IIDXLDJForce720p,
|
||||
@@ -201,6 +199,7 @@ namespace launcher {
|
||||
LoadStubs,
|
||||
AdjustOrientation,
|
||||
spice2x_AutoOrientation,
|
||||
ChangeResolution,
|
||||
LogLevel,
|
||||
EAAutomap,
|
||||
EANetdump,
|
||||
@@ -241,7 +240,10 @@ namespace launcher {
|
||||
IIDXWindowedSubscreenBorderless,
|
||||
IIDXWindowedSubscreenAlwaysOnTop,
|
||||
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,
|
||||
@@ -281,6 +285,9 @@ namespace launcher {
|
||||
LovePlusPrinterJPGQuality,
|
||||
OptionConflictResolution,
|
||||
OtocaCamHook,
|
||||
DisableHighResTimer,
|
||||
EnableICMPHook,
|
||||
AutoElevate,
|
||||
};
|
||||
|
||||
enum class OptionsCategory {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "launcher.h"
|
||||
#include "logger.h"
|
||||
#include "nvapi/nvapi.h"
|
||||
#include "sdk/sdk.h"
|
||||
|
||||
namespace launcher {
|
||||
|
||||
@@ -23,6 +24,8 @@ namespace launcher {
|
||||
// therefore, subsystems need to be guarded against multiple unload attempts
|
||||
log_info("launcher", "stopping subsystems");
|
||||
|
||||
sdk::fini_sdk_modules();
|
||||
|
||||
// reset monitor settings
|
||||
reset_monitor_on_exit();
|
||||
|
||||
|
||||
@@ -67,7 +67,9 @@ namespace superexit {
|
||||
}
|
||||
}
|
||||
|
||||
bool async_key_exit = GetAsyncKeyState(VK_MENU) && GetAsyncKeyState(VK_F4);
|
||||
bool async_key_exit =
|
||||
(GetAsyncKeyState(VK_MENU) & 0x8000) != 0 &&
|
||||
(GetAsyncKeyState(VK_F4) & 0x8000) != 0;
|
||||
|
||||
bool overlay_exit = false;
|
||||
auto buttons = games::get_buttons_overlay(eamuse_get_game());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "eamuse.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
|
||||
@@ -9,6 +10,7 @@
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
#include "overlay/overlay.h"
|
||||
@@ -43,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] = {
|
||||
@@ -50,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
|
||||
@@ -286,6 +303,7 @@ void eamuse_coin_start_thread() {
|
||||
COIN_INPUT_THREAD = new std::thread([]() {
|
||||
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
|
||||
static bool COIN_INPUT_KEY_STATE = false;
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
while (COIN_INPUT_THREAD_ACTIVE) {
|
||||
|
||||
// check input key
|
||||
@@ -305,7 +323,7 @@ void eamuse_coin_start_thread() {
|
||||
}
|
||||
|
||||
// once every two frames
|
||||
Sleep(1000 / 30);
|
||||
timer.sleep(1000 / 30);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -322,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[] = {
|
||||
@@ -340,17 +377,23 @@ void eamuse_pin_macro_start_thread() {
|
||||
size_t pin_index[2] = {PIN_MACRO_VALUES[0].length(), PIN_MACRO_VALUES[1].length()};
|
||||
|
||||
std::optional<uint8_t> active_unit = std::nullopt;
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
while (PIN_MACRO_THREAD_ACTIVE) {
|
||||
// wait for key press
|
||||
// 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;
|
||||
@@ -359,7 +402,7 @@ void eamuse_pin_macro_start_thread() {
|
||||
}
|
||||
|
||||
if (!active_unit.has_value()) {
|
||||
Sleep(20);
|
||||
timer.sleep(20);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -375,22 +418,22 @@ void eamuse_pin_macro_start_thread() {
|
||||
eamuse_set_keypad_overrides(unit, keypad_overrides[char_index]);
|
||||
}
|
||||
pin_index[unit]++;
|
||||
Sleep(100);
|
||||
timer.sleep(100);
|
||||
|
||||
// clear
|
||||
eamuse_set_keypad_overrides(unit, 0);
|
||||
Sleep(50);
|
||||
timer.sleep(50);
|
||||
|
||||
// end of PIN
|
||||
if (pin_index[unit] == PIN_MACRO_VALUES[unit].length()) {
|
||||
active_unit = std::nullopt;
|
||||
Sleep(120);
|
||||
timer.sleep(120);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Sleep(200);
|
||||
timer.sleep(200);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -402,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);
|
||||
|
||||
@@ -38,6 +38,9 @@ namespace wintouchemu {
|
||||
bool INJECT_MOUSE_AS_WM_TOUCH = false;
|
||||
bool LOG_FPS = false;
|
||||
bool ADD_TOUCH_FLAG_PRIMARY = false;
|
||||
|
||||
// state
|
||||
double last_touch_event = 0.0;
|
||||
|
||||
static inline bool is_emu_enabled() {
|
||||
return FORCE || !is_touch_available("wintouchemu::is_emu_enabled") || GRAPHICS_SHOW_CURSOR;
|
||||
@@ -248,7 +251,10 @@ namespace wintouchemu {
|
||||
touch_input->y -= SPICETOUCH_TOUCH_Y;
|
||||
}
|
||||
|
||||
// log_misc("wintouchemu", "mouse state ({}, {})", to_string(touch_input->x), to_string(touch_input->y));
|
||||
// log_misc(
|
||||
// "wintouchemu",
|
||||
// "mouse state ({}, {}) event={}",
|
||||
// to_string(touch_input->x), to_string(touch_input->y), mouse_state.touch_event);
|
||||
|
||||
auto valid = true;
|
||||
if (overlay::OVERLAY) {
|
||||
@@ -410,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 {
|
||||
@@ -439,6 +445,8 @@ namespace wintouchemu {
|
||||
}
|
||||
}
|
||||
|
||||
const auto now = get_performance_milliseconds();
|
||||
|
||||
// update touch events
|
||||
if (hWnd != nullptr) {
|
||||
|
||||
@@ -458,6 +466,7 @@ namespace wintouchemu {
|
||||
|
||||
// check if new events are available
|
||||
if (event_count > 0) {
|
||||
last_touch_event = now;
|
||||
|
||||
// send fake event to make the game update it's touch inputs
|
||||
auto wndProc = (WNDPROC) GetWindowLongPtr(hWnd, GWLP_WNDPROC);
|
||||
@@ -484,7 +493,12 @@ namespace wintouchemu {
|
||||
// value from GetTouchInputInfo or fail to read dwFlags for valid events, so it's not OK to
|
||||
// send empty events when the mouse button is not clicked/released
|
||||
if (hWnd != nullptr && USE_MOUSE) {
|
||||
bool button_pressed = ((GetKeyState(VK_LBUTTON) & 0x100) != 0);
|
||||
bool button_pressed = get_async_primary_mouse();
|
||||
|
||||
// if there was a touch event in the last 500 ms, don't insert new button presses
|
||||
if (button_pressed && (now - last_touch_event) < 500) {
|
||||
button_pressed = false;
|
||||
}
|
||||
|
||||
// figure out what kind of touch event to simulate
|
||||
if (button_pressed && !mouse_state.last_button_pressed) {
|
||||
|
||||
@@ -351,7 +351,8 @@ void ImGui_ImplSpice_NewFrame() {
|
||||
|
||||
// only process new input from devices if window is in focus
|
||||
// (when not in focus, all mouse/keyboard events are treated as released)
|
||||
const auto accept_new_input = superexit::has_focus() && !rawinput::OS_WINDOW_ACTIVE;
|
||||
const auto overlay_visible = overlay::OVERLAY && overlay::OVERLAY->get_active();
|
||||
const auto accept_new_input = superexit::has_focus() && !rawinput::OS_WINDOW_ACTIVE && overlay_visible;
|
||||
|
||||
// remember old state
|
||||
std::array<BYTE, VKEY_MAX> KeysDownOld;
|
||||
@@ -366,9 +367,11 @@ void ImGui_ImplSpice_NewFrame() {
|
||||
g_KeysDown.fill(false);
|
||||
|
||||
// apply windows mouse buttons
|
||||
g_MouseDown[ImGuiMouseButton_Left] |= (get_async_primary_mouse()) != 0;
|
||||
g_MouseDown[ImGuiMouseButton_Right] |= (get_async_secondary_mouse()) != 0;
|
||||
g_MouseDown[ImGuiMouseButton_Middle] |= (GetAsyncKeyState(VK_MBUTTON)) != 0;
|
||||
if (accept_new_input) {
|
||||
g_MouseDown[ImGuiMouseButton_Left] |= get_async_primary_mouse();
|
||||
g_MouseDown[ImGuiMouseButton_Right] |= get_async_secondary_mouse();
|
||||
g_MouseDown[ImGuiMouseButton_Middle] |= (GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0;
|
||||
}
|
||||
|
||||
// read new keys state
|
||||
static long mouse_wheel_last = 0;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -948,7 +948,7 @@ namespace overlay::windows {
|
||||
|
||||
// grab current keyboard state
|
||||
for (unsigned short int i = 0x01; i < 0xFF; i++) {
|
||||
buttons_keyboard_state[i] = GetAsyncKeyState(i) != 0;
|
||||
buttons_keyboard_state[i] = (GetAsyncKeyState(i) & 0x8000) != 0;
|
||||
}
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
|
||||
@@ -1615,7 +1615,7 @@ namespace overlay::windows {
|
||||
// use care when iterating over the result (could result in torn reads)
|
||||
bool keyboard_state_new[sizeof(buttons_keyboard_state)];
|
||||
for (size_t i = 0; i < sizeof(keyboard_state_new); i++) {
|
||||
keyboard_state_new[i] = GetAsyncKeyState(i) != 0;
|
||||
keyboard_state_new[i] = (GetAsyncKeyState(i) & 0x8000) != 0;
|
||||
}
|
||||
|
||||
// detect key presses
|
||||
@@ -2539,9 +2539,9 @@ namespace overlay::windows {
|
||||
}
|
||||
}
|
||||
|
||||
// hide deadzone for circular analog since it doesn't make any sense
|
||||
// hide deadzone for circular analog since it doesn't make any sense (unless in relative mode)
|
||||
if ((device->type == rawinput::HID || device->type == rawinput::MIDI) &&
|
||||
analog.getType() != GameAPI::Analogs::AnalogType::Circular) {
|
||||
((analog.getType() != GameAPI::Analogs::AnalogType::Circular) || analog.isRelativeMode())) {
|
||||
auto deadzone = analog.getDeadzone();
|
||||
|
||||
// for back compat (before each analog had a type)
|
||||
@@ -2585,16 +2585,46 @@ namespace overlay::windows {
|
||||
if (analog.getType() == GameAPI::Analogs::AnalogType::Circular) {
|
||||
// smoothing
|
||||
bool smoothing = analog.getSmoothing();
|
||||
ImGui::BeginDisabled(analog.isRelativeMode());
|
||||
ImGui::Checkbox("Smooth Axis (adds latency)", &smoothing);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Apply a moving average algorithm; intended for angular input (knobs, turntables). "
|
||||
"Adds a slight bit of latency to input as the algorithm averages out recent input. "
|
||||
"Only use in dire situations where the input is too jittery for the game.");
|
||||
ImGui::EndDisabled();
|
||||
if (smoothing != analog.getSmoothing()) {
|
||||
analog.setSmoothing(smoothing);
|
||||
}
|
||||
|
||||
// relative input mode
|
||||
bool relative_analog = analog.isRelativeMode();
|
||||
ImGui::Checkbox("Relative Axis", &relative_analog);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Use relative directional input instead of positional values. "
|
||||
"Can be used to translate analog sticks to knob input.\n\n"
|
||||
"At default settings, max speed is one revolution per second.\n\n"
|
||||
"Adjust sensitivity to increase/decrease max speed, and deadzone to prevent jitter.");
|
||||
if (relative_analog != analog.isRelativeMode()) {
|
||||
analog.setRelativeMode(relative_analog);
|
||||
}
|
||||
}
|
||||
|
||||
// delay
|
||||
int delay = analog.getDelayMs();
|
||||
if (ImGui::InputInt("Delay (ms)", &delay, 1, 1)) {
|
||||
delay = std::clamp(delay, 0, 250);
|
||||
analog.setDelayMs(delay);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Adds an artificial delay, in milliseconds.\n\n"
|
||||
"Value read by the game engine will be delayed AT MOST this amount of time (could be slightly less).\n\n"
|
||||
"To minimize quantization error, delay value should be a multiple of the game's poll rate, "
|
||||
"add 0.5ms of buffer, and then round up to the nearest millisecond.\n\n"
|
||||
"If you aren't sure, assume the game polls for input at the same rate as the display FPS."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5406,6 +5436,8 @@ namespace overlay::windows {
|
||||
a.setInvert(ta.invert);
|
||||
a.setSmoothing(ta.smoothing);
|
||||
a.setMultiplier(ta.multiplier);
|
||||
a.setRelativeMode(ta.relative_mode);
|
||||
a.setDelayMs(ta.delay_ms);
|
||||
::Config::getInstance().updateBinding(game, a);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ namespace overlay::windows {
|
||||
bool invert = false;
|
||||
bool smoothing = false;
|
||||
int multiplier = 1;
|
||||
bool relative_mode = false;
|
||||
uint32_t delay_ms = 0;
|
||||
|
||||
bool is_device() const { return !device_identifier.empty(); }
|
||||
bool is_unbound() const { return device_identifier.empty() && index == 0xFF; }
|
||||
@@ -87,6 +89,8 @@ namespace overlay::windows {
|
||||
invert = a.getInvert();
|
||||
smoothing = a.getSmoothing();
|
||||
multiplier = a.getMultiplier();
|
||||
relative_mode = a.isRelativeMode();
|
||||
delay_ms = a.getDelayMs();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "external/robin_hood.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
@@ -155,8 +156,9 @@ void rawinput::RawInputManager::input_hwnd_create() {
|
||||
});
|
||||
|
||||
// wait for window creation being done
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
while (!this->input_hwnd) {
|
||||
Sleep(1);
|
||||
timer.sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2983,4 +2985,4 @@ void rawinput::RawInputManager::remove_callback_midi(void * data, const std::fun
|
||||
[data, callback](MidiCallback const &cb) {
|
||||
return cb.data == data && cb.f.target<void>() == callback.target<void>();
|
||||
}), this->callback_midi.end());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
#include "structuredmessage.h"
|
||||
|
||||
@@ -217,6 +218,7 @@ bool Reader::set_comm_state(DWORD BaudRate) {
|
||||
}
|
||||
|
||||
bool Reader::wait_for_handshake() {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// baud rates
|
||||
DWORD baud_rates[] = { CBR_57600, CBR_38400, CBR_19200, CBR_9600 };
|
||||
@@ -268,7 +270,7 @@ bool Reader::wait_for_handshake() {
|
||||
}
|
||||
|
||||
// sleep
|
||||
Sleep(50);
|
||||
timer.sleep(50);
|
||||
}
|
||||
|
||||
log_warning("reader", "{}: no handshake received for {} baud", this->port, baud_rates[i]);
|
||||
@@ -405,6 +407,7 @@ void start_reader_thread(const std::string &port, int id) {
|
||||
READER_THREAD_RUNNING = true;
|
||||
READER_THREADS.push_back(new std::thread([port, id]() {
|
||||
log_info("reader", "{}: starting reader thread", port);
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
while (READER_THREAD_RUNNING) {
|
||||
|
||||
@@ -442,21 +445,22 @@ void start_reader_thread(const std::string &port, int id) {
|
||||
}
|
||||
|
||||
if (did_read_card) {
|
||||
Sleep(2500);
|
||||
timer.sleep(2500);
|
||||
}
|
||||
|
||||
Sleep(20);
|
||||
timer.sleep(20);
|
||||
}
|
||||
}
|
||||
|
||||
// sleep between reader connection retries
|
||||
if (READER_THREAD_RUNNING)
|
||||
Sleep(5000);
|
||||
timer.sleep(5000);
|
||||
}
|
||||
}));
|
||||
|
||||
// wait for thread to start
|
||||
Sleep(10);
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
timer.sleep(10);
|
||||
}
|
||||
|
||||
void stop_reader_thread() {
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
#pragma once
|
||||
#ifndef SPICE_SDK_H
|
||||
#define SPICE_SDK_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define SPICE_SDK_ENTRY_POINT extern "C" __declspec(dllexport) int __cdecl
|
||||
#else
|
||||
#define SPICE_SDK_ENTRY_POINT __declspec(dllexport) int __cdecl
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum SPICE_SDK_STATUS_CODE {
|
||||
SPICE_SDK_STATUS_SUCCESS = 0,
|
||||
|
||||
// 000: generic
|
||||
SPICE_SDK_STATUS_GENERIC_ERROR = 1,
|
||||
SPICE_SDK_STATUS_NOT_INITIALIZED = 2,
|
||||
SPICE_SDK_STATUS_NOT_SUPPORTED = 3,
|
||||
SPICE_SDK_STATUS_TOO_SMALL = 4,
|
||||
SPICE_SDK_STATUS_TOO_LATE = 5,
|
||||
|
||||
// 1000: invalid args
|
||||
SPICE_SDK_STATUS_INVALID_ARGUMENT_1 = 1001,
|
||||
SPICE_SDK_STATUS_INVALID_ARGUMENT_2 = 1002,
|
||||
SPICE_SDK_STATUS_INVALID_ARGUMENT_3 = 1003,
|
||||
SPICE_SDK_STATUS_INVALID_ARGUMENT_4 = 1004,
|
||||
SPICE_SDK_STATUS_INVALID_ARGUMENT_5 = 1005,
|
||||
|
||||
} SPICE_SDK_STATUS_CODE;
|
||||
|
||||
typedef enum SPICE_SDK_LOG_LEVEL {
|
||||
SPICE_SDK_LOG_LEVEL_MISC = 0,
|
||||
SPICE_SDK_LOG_LEVEL_INFO = 1,
|
||||
SPICE_SDK_LOG_LEVEL_WARNING = 2,
|
||||
SPICE_SDK_LOG_LEVEL_FATAL = 3,
|
||||
} SPICE_SDK_LOG_LEVEL;
|
||||
|
||||
typedef struct SPICE_SDK_TOUCH_POINT {
|
||||
uint32_t id;
|
||||
int x;
|
||||
int y;
|
||||
} SPICE_SDK_TOUCH_POINT;
|
||||
|
||||
typedef struct SPICE_SDK_GAME_INFO {
|
||||
char name[64]; // null-terminated
|
||||
} SPICE_SDK_GAME_INFO;
|
||||
|
||||
typedef struct SPICE_SDK_AVS_INFO {
|
||||
char model[4]; // "MDX", null-terminated
|
||||
char dest; // J
|
||||
char spec; // A
|
||||
char rev; // A
|
||||
char ext[11]; // "2025061002", null-terminated
|
||||
} SPICE_SDK_AVS_INFO;
|
||||
|
||||
// get_game_info (v0.1 and up)
|
||||
//
|
||||
// get info about the currently running game
|
||||
//
|
||||
// info: receives game info
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_get_game_info_func)(
|
||||
SPICE_SDK_GAME_INFO *info
|
||||
);
|
||||
|
||||
// get_avs_info (v0.1 and up)
|
||||
//
|
||||
// get AVS info (model, dest, spec, rev, ext)
|
||||
//
|
||||
// info: receives AVS info
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_get_avs_info_func)(
|
||||
SPICE_SDK_AVS_INFO *info
|
||||
);
|
||||
|
||||
// log (v0.1 and up)
|
||||
// logs a message to the log
|
||||
// writing a FATAL message will terminate spice, only use in catastrophic failure
|
||||
//
|
||||
// level: see log level enum
|
||||
// module: short string that identifies the facility / module / submodule
|
||||
// message: the message to log
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_log_func)(
|
||||
SPICE_SDK_LOG_LEVEL level,
|
||||
const char *module,
|
||||
const char *message
|
||||
);
|
||||
|
||||
// get_button (v0.1 and up)
|
||||
// gets the button state
|
||||
//
|
||||
// button_id: ID of the button; see spicesdk_io.h for named values
|
||||
// pressed: (optional) is the button pressed?
|
||||
// velocity: (optional) MIDI velocity of the button
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_get_button_func)(
|
||||
uint32_t button_id,
|
||||
bool *pressed,
|
||||
float *velocity
|
||||
);
|
||||
|
||||
// set_button (v0.1 and up)
|
||||
// sets or clears the button override
|
||||
//
|
||||
// make sure to hold the button long enough for the game's I/O engine to pick up
|
||||
// usually, one or two frames
|
||||
//
|
||||
// button_id: ID of the button; see spicesdk_io.h for named values
|
||||
// pressed: true to set the button override (permanently set the button to be ON until cleared),
|
||||
// false to clear the override (allow user's controller to provide input again)
|
||||
// velocity: MIDI velocity of the button; only valid when pressed is true
|
||||
// can be between 0.0 and 1.0, inclusive
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_set_button_func)(
|
||||
uint32_t button_id,
|
||||
bool pressed,
|
||||
float velocity
|
||||
);
|
||||
|
||||
// get_analog (v0.1 and up)
|
||||
// gets the analog state
|
||||
//
|
||||
// button_id: ID of the button; see spicesdk_io.h for named values
|
||||
// value: receives the state of the analog
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_get_analog_func)(
|
||||
uint32_t analog_id,
|
||||
float *value
|
||||
);
|
||||
|
||||
// set_analog (v0.1 and up)
|
||||
// sets or clears the analog override
|
||||
//
|
||||
// analog_id: ID of the analog; see spicesdk_io.h for named values
|
||||
// override_active: true to set override (gain exclusive control)
|
||||
// false to clear it (allow user's controller provide input again)
|
||||
// value: value of the analog; only valid when override_active is true
|
||||
// can be between 0.0 and 1.0, inclusive
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_set_analog_func)(
|
||||
uint32_t analog_id,
|
||||
bool override_active,
|
||||
float value
|
||||
);
|
||||
|
||||
// get_light (v0.1 and up)
|
||||
// gets the last observed value of a light
|
||||
//
|
||||
// light_id: ID of the light; see spicesdk_io.h for named values
|
||||
// value: output parameter for the light value; 0.0 to 1.0
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_get_light_func)(
|
||||
uint32_t light_id,
|
||||
float *value
|
||||
);
|
||||
|
||||
// set_light (v0.1 and up)
|
||||
// sets or clears the light override
|
||||
//
|
||||
// light_id: ID of the light; see spicesdk_io.h for named values
|
||||
// light_value: output parameter for the light value; 0.0 to 1.0
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_set_light_func)(
|
||||
uint32_t light_id,
|
||||
bool override_active,
|
||||
float light_value
|
||||
);
|
||||
|
||||
// set_touch (v0.1 and up)
|
||||
// adds or updates touch points
|
||||
//
|
||||
// points: array of touch points to add or update
|
||||
// count: number of touch points in the array
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_set_touch_func)(
|
||||
const SPICE_SDK_TOUCH_POINT *points,
|
||||
uint32_t count
|
||||
);
|
||||
|
||||
// clear_touch (v0.1 and up)
|
||||
// clears touch points (i.e., no longer being touched)
|
||||
//
|
||||
// ids: array of touch point IDs to clear
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_clear_touch_func)(
|
||||
const uint32_t *ids,
|
||||
uint32_t count
|
||||
);
|
||||
|
||||
// insert_card (v0.1 and up)
|
||||
// simulates inserting an e-amuse card with the given ID
|
||||
//
|
||||
// unit: 0 for player 1, 1 for player 2
|
||||
// card_id: null-terminated string of the card ID
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_insert_card_func)(
|
||||
uint8_t unit,
|
||||
const char *card_id
|
||||
);
|
||||
|
||||
// set_keypad (v0.1 and up)
|
||||
// sets keypad state
|
||||
//
|
||||
// make sure to hold the button long enough for the game to pick up
|
||||
// 70ms is usually sufficient, except for DDR which needs 150ms
|
||||
//
|
||||
// unit: 0 for player 1, 1 for player 2
|
||||
// key: '0' to '9' for numbers, 'A' for 00, 'D' for decimal point, 0 or '\0' to release all keys
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_set_keypad_func)(
|
||||
uint8_t unit,
|
||||
char key
|
||||
);
|
||||
|
||||
typedef struct SPICE_SDK_V0 {
|
||||
uint32_t size;
|
||||
|
||||
spice_sdk_log_func *log;
|
||||
|
||||
spice_sdk_get_game_info_func *get_game_info;
|
||||
spice_sdk_get_avs_info_func *get_avs_info;
|
||||
|
||||
spice_sdk_get_button_func *get_button;
|
||||
spice_sdk_set_button_func *set_button;
|
||||
|
||||
spice_sdk_get_analog_func *get_analog;
|
||||
spice_sdk_set_analog_func *set_analog;
|
||||
|
||||
spice_sdk_get_light_func *get_light;
|
||||
spice_sdk_set_light_func *set_light;
|
||||
|
||||
spice_sdk_set_touch_func *set_touch;
|
||||
spice_sdk_clear_touch_func *clear_touch;
|
||||
|
||||
spice_sdk_insert_card_func *insert_card;
|
||||
spice_sdk_set_keypad_func *set_keypad;
|
||||
|
||||
} SPICE_SDK_V0;
|
||||
|
||||
typedef void (__cdecl spice_sdk_destroy_callback_func)(
|
||||
void
|
||||
);
|
||||
|
||||
// init (v0.1 and up)
|
||||
//
|
||||
// version: supply 0
|
||||
// destroy_callback: supply a function pointer that will be called when spice
|
||||
// is shutting down
|
||||
// sdk_functions: supply a pointer to SPICE_SDK_V0; ensure size field is initialized
|
||||
// to sizeof(SPICE_SDK_V0) before calling this function
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_init_func)(
|
||||
uint32_t version,
|
||||
spice_sdk_destroy_callback_func *destroy_callback,
|
||||
void *sdk_functions
|
||||
);
|
||||
|
||||
typedef int (__cdecl spice_sdk_entry_point_func)(
|
||||
spice_sdk_init_func *init
|
||||
);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // SPICE_SDK_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,98 @@
|
||||
#include <format>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <windows.h>
|
||||
|
||||
#include "sdk/include/spicesdk.h"
|
||||
#include "sdk/include/spicesdk_io.h"
|
||||
|
||||
// this sample assumes that the game is DDR
|
||||
|
||||
// convenience wrapper for logging; feel free to add other levels
|
||||
#define LOG_INFO(message) spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0_cpp", message)
|
||||
|
||||
static SPICE_SDK_V0 spice = {};
|
||||
|
||||
static spice_sdk_destroy_callback_func destroy_callback;
|
||||
static std::jthread worker_thread;
|
||||
static void worker_thread_main(std::stop_token stop_token);
|
||||
|
||||
// main entry point into the DLL
|
||||
// spice executable will call into this shortly after the game is partially
|
||||
// initialized (but not quite fully booted just yet)
|
||||
SPICE_SDK_ENTRY_POINT
|
||||
spice_sdk_entry_point(
|
||||
spice_sdk_init_func *init
|
||||
)
|
||||
{
|
||||
SPICE_SDK_STATUS_CODE status;
|
||||
|
||||
// SPICE_SDK_V0 must be zeroed AND the size field must be set
|
||||
spice.size = sizeof(spice);
|
||||
|
||||
// initialize the SDK - can be called any time, but we'll do it here
|
||||
status = init(0, destroy_callback, &spice);
|
||||
|
||||
// always check for return value; failure could be usage error,
|
||||
// or the spice executable doesn't support this SDK version
|
||||
// check the log messages from sdk:
|
||||
if (status != SPICE_SDK_STATUS_SUCCESS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOG_INFO("plugin loaded");
|
||||
|
||||
// spin up a worker thread
|
||||
worker_thread = std::jthread(worker_thread_main);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
__cdecl
|
||||
destroy_callback(
|
||||
void
|
||||
)
|
||||
{
|
||||
LOG_INFO("plugin unloading");
|
||||
if (worker_thread.joinable()) {
|
||||
worker_thread.request_stop();
|
||||
worker_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
struct ArrowButton {
|
||||
int key;
|
||||
SPICE_SDK_DDR_BUTTONS button;
|
||||
const char* name;
|
||||
bool previous_state;
|
||||
};
|
||||
|
||||
static ArrowButton arrow_buttons[] = {
|
||||
{ VK_UP, DDR_Button_P1_PANEL_UP, "UP", false },
|
||||
{ VK_DOWN, DDR_Button_P1_PANEL_DOWN, "DOWN", false },
|
||||
{ VK_LEFT, DDR_Button_P1_PANEL_LEFT, "LEFT", false },
|
||||
{ VK_RIGHT, DDR_Button_P1_PANEL_RIGHT, "RIGHT", false },
|
||||
};
|
||||
|
||||
// worker thread for I/O
|
||||
static void worker_thread_main(std::stop_token stop_token) {
|
||||
while (!stop_token.stop_requested()) {
|
||||
for (auto& arrow : arrow_buttons) {
|
||||
// check for ctrl + arrow keys and trigger p1 pad arrows
|
||||
if (((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) &&
|
||||
((GetAsyncKeyState(arrow.key) & 0x8000) != 0)) {
|
||||
spice.set_button(arrow.button, true, 1.f);
|
||||
|
||||
if (!arrow.previous_state) {
|
||||
LOG_INFO(std::format("let me hear you say: {}", arrow.name).c_str());
|
||||
arrow.previous_state = true;
|
||||
}
|
||||
} else {
|
||||
spice.set_button(arrow.button, false, 0.f);
|
||||
arrow.previous_state = false;
|
||||
}
|
||||
}
|
||||
|
||||
Sleep(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
LIBRARY sdk_sample_v0_cpp
|
||||
|
||||
EXPORTS
|
||||
spice_sdk_entry_point
|
||||
@@ -0,0 +1,318 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
#include <process.h>
|
||||
|
||||
#include "sdk/include/spicesdk.h"
|
||||
#include "sdk/include/spicesdk_io.h"
|
||||
|
||||
static SPICE_SDK_V0 spice;
|
||||
|
||||
static spice_sdk_destroy_callback_func destroy_callback;
|
||||
|
||||
static void test_logging();
|
||||
static void test_game_info();
|
||||
static void test_avs_info();
|
||||
|
||||
static void get_buttons();
|
||||
static void set_buttons();
|
||||
static void clear_buttons();
|
||||
|
||||
static void get_analogs();
|
||||
static void set_analogs();
|
||||
static void clear_analogs();
|
||||
|
||||
static void get_lights();
|
||||
static void set_lights();
|
||||
static void clear_lights();
|
||||
|
||||
static void set_touch();
|
||||
static void clear_touch();
|
||||
static void insert_card();
|
||||
static void set_keypad();
|
||||
static void clear_keypad();
|
||||
|
||||
static HANDLE worker_stop_event;
|
||||
static HANDLE worker_handle;
|
||||
static unsigned __stdcall worker_thread(void *arg);
|
||||
|
||||
// this sample assumes that the game is IIDX, but it doesn't check for it.
|
||||
|
||||
SPICE_SDK_ENTRY_POINT
|
||||
spice_sdk_entry_point(
|
||||
spice_sdk_init_func *init
|
||||
)
|
||||
{
|
||||
SPICE_SDK_STATUS_CODE status;
|
||||
|
||||
memset(&spice, 0, sizeof(spice));
|
||||
spice.size = sizeof(spice);
|
||||
status = init(0, destroy_callback, &spice);
|
||||
if (status != SPICE_SDK_STATUS_SUCCESS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "plugin loaded");
|
||||
test_logging();
|
||||
test_game_info();
|
||||
test_avs_info();
|
||||
|
||||
worker_stop_event = CreateEventA(NULL, TRUE, FALSE, NULL);
|
||||
if (!worker_stop_event) {
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_WARNING, "sample_v0", "failed to create worker stop event");
|
||||
return 0;
|
||||
}
|
||||
|
||||
worker_handle = (HANDLE)_beginthreadex(
|
||||
NULL, // security
|
||||
0, // stack size
|
||||
worker_thread, // function
|
||||
NULL, // argument
|
||||
0, // flags
|
||||
NULL // thread id
|
||||
);
|
||||
if (!worker_handle) {
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_WARNING, "sample_v0", "failed to create worker thread");
|
||||
CloseHandle(worker_stop_event);
|
||||
worker_stop_event = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
__cdecl
|
||||
destroy_callback(
|
||||
void
|
||||
)
|
||||
{
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "plugin unloaded");
|
||||
if (worker_stop_event) {
|
||||
SetEvent(worker_stop_event);
|
||||
}
|
||||
if (worker_handle) {
|
||||
WaitForSingleObject(worker_handle, INFINITE);
|
||||
CloseHandle(worker_handle);
|
||||
worker_handle = NULL;
|
||||
}
|
||||
if (worker_stop_event) {
|
||||
CloseHandle(worker_stop_event);
|
||||
worker_stop_event = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned __stdcall worker_thread(void *arg) {
|
||||
int phase = 0;
|
||||
while (WaitForSingleObject(worker_stop_event, 0) == WAIT_TIMEOUT) {
|
||||
phase += 1;
|
||||
switch (phase) {
|
||||
case 1:
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "get buttons...");
|
||||
get_buttons();
|
||||
break;
|
||||
case 2:
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set buttons...");
|
||||
set_buttons();
|
||||
break;
|
||||
case 3:
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear buttons...");
|
||||
clear_buttons();
|
||||
break;
|
||||
case 4:
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "get analogs...");
|
||||
get_analogs();
|
||||
break;
|
||||
case 5:
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set analogs...");
|
||||
set_analogs();
|
||||
break;
|
||||
case 6:
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear analogs...");
|
||||
clear_analogs();
|
||||
break;
|
||||
case 7:
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "get lights...");
|
||||
get_lights();
|
||||
break;
|
||||
case 8:
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set lights...");
|
||||
set_lights();
|
||||
break;
|
||||
case 9:
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear lights...");
|
||||
clear_lights();
|
||||
break;
|
||||
case 10:
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set touch...");
|
||||
set_touch();
|
||||
break;
|
||||
case 11:
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear touch...");
|
||||
clear_touch();
|
||||
break;
|
||||
case 12:
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "insert card...");
|
||||
insert_card();
|
||||
break;
|
||||
case 13:
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set keypad...");
|
||||
set_keypad();
|
||||
break;
|
||||
case 14:
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear keypad...");
|
||||
clear_keypad();
|
||||
break;
|
||||
default:
|
||||
phase = 0;
|
||||
break;
|
||||
}
|
||||
if (phase != 0) {
|
||||
WaitForSingleObject(worker_stop_event, 3000);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_logging() {
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_MISC, "sample_v0", "this is a misc message");
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "this is an info message");
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_WARNING, "sample_v0", "this is a warning message");
|
||||
}
|
||||
|
||||
static void test_game_info() {
|
||||
SPICE_SDK_GAME_INFO info;
|
||||
SPICE_SDK_STATUS_CODE status;
|
||||
|
||||
status = spice.get_game_info(&info);
|
||||
if (status != SPICE_SDK_STATUS_SUCCESS) {
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_WARNING, "sample_v0", "get_game_info failed");
|
||||
return;
|
||||
}
|
||||
char log_message[128];
|
||||
snprintf(log_message, sizeof(log_message), "game info - name: %s", info.name);
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", log_message);
|
||||
}
|
||||
|
||||
static void test_avs_info() {
|
||||
SPICE_SDK_AVS_INFO info;
|
||||
SPICE_SDK_STATUS_CODE status;
|
||||
|
||||
status = spice.get_avs_info(&info);
|
||||
if (status != SPICE_SDK_STATUS_SUCCESS) {
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_WARNING, "sample_v0", "get_avs_info failed");
|
||||
return;
|
||||
}
|
||||
|
||||
char log_message[128];
|
||||
snprintf(
|
||||
log_message,
|
||||
sizeof(log_message),
|
||||
"avs - model: %s, dest: %c, spec: %c, rev: %c, ext: %s",
|
||||
info.model, info.dest, info.spec, info.rev, info.ext);
|
||||
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", log_message);
|
||||
}
|
||||
|
||||
static void get_buttons() {
|
||||
bool pressed;
|
||||
float velocity;
|
||||
spice.get_button(IIDX_Button_P1_Headphone, &pressed, &velocity);
|
||||
|
||||
char log_message[128];
|
||||
snprintf(
|
||||
log_message,
|
||||
sizeof(log_message),
|
||||
"button P1_Headphone pressed: %s, velocity: %.2f",
|
||||
pressed ? "ON" : "off", velocity);
|
||||
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", log_message);
|
||||
}
|
||||
|
||||
static void set_buttons() {
|
||||
spice.set_button(IIDX_Button_P1_1, true, 0.3f);
|
||||
spice.set_button(IIDX_Button_P1_3, true, 0.5f);
|
||||
spice.set_button(IIDX_Button_P1_5, true, 0.7f);
|
||||
}
|
||||
|
||||
static void clear_buttons() {
|
||||
spice.set_button(IIDX_Button_P1_1, false, 0.f);
|
||||
spice.set_button(IIDX_Button_P1_3, false, 0.f);
|
||||
spice.set_button(IIDX_Button_P1_5, false, 0.f);
|
||||
}
|
||||
|
||||
static void get_analogs() {
|
||||
float value;
|
||||
spice.get_analog(IIDX_Analog_TT_P1, &value);
|
||||
|
||||
char log_message[128];
|
||||
snprintf(
|
||||
log_message,
|
||||
sizeof(log_message),
|
||||
"analog TT_P1: %.2f",
|
||||
value);
|
||||
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", log_message);
|
||||
}
|
||||
|
||||
static void set_analogs() {
|
||||
spice.set_analog(IIDX_Analog_TT_P1, true, 0.25f);
|
||||
spice.set_analog(IIDX_Analog_TT_P2, true, 0.75f);
|
||||
}
|
||||
|
||||
static void clear_analogs() {
|
||||
spice.set_analog(IIDX_Analog_TT_P1, false, 0.f);
|
||||
spice.set_analog(IIDX_Analog_TT_P2, false, 0.f);
|
||||
}
|
||||
|
||||
static void get_lights() {
|
||||
float value;
|
||||
SPICE_SDK_STATUS_CODE status;
|
||||
|
||||
status = spice.get_light(IIDX_Light_TT_P1_Resistance, &value);
|
||||
if (status == SPICE_SDK_STATUS_SUCCESS) {
|
||||
char log_message[64];
|
||||
snprintf(log_message, sizeof(log_message), "P1 TT resistance value: %.2f", value);
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", log_message);
|
||||
} else {
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_WARNING, "sample_v0", "get_light failed");
|
||||
}
|
||||
}
|
||||
|
||||
static void set_lights() {
|
||||
spice.set_light(IIDX_Light_P1_Start, true, 1.f);
|
||||
}
|
||||
|
||||
static void clear_lights() {
|
||||
spice.set_light(IIDX_Light_P1_Start, false, 0.f);
|
||||
}
|
||||
|
||||
static void set_touch() {
|
||||
SPICE_SDK_TOUCH_POINT points[2] = {
|
||||
{ .id = 1, .x = 100, .y = 200 },
|
||||
{ .id = 2, .x = 300, .y = 400 },
|
||||
};
|
||||
spice.set_touch(points, 2);
|
||||
}
|
||||
|
||||
static void clear_touch() {
|
||||
uint32_t ids[2] = { 1, 2 };
|
||||
spice.clear_touch(ids, 2);
|
||||
}
|
||||
|
||||
static void insert_card() {
|
||||
spice.insert_card(0, "E004010000001234");
|
||||
}
|
||||
|
||||
static void set_keypad() {
|
||||
SPICE_SDK_STATUS_CODE ret = spice.set_keypad(0, '3');
|
||||
if (ret != SPICE_SDK_STATUS_SUCCESS) {
|
||||
char log_message[64];
|
||||
snprintf(log_message, sizeof(log_message), "set_keypad failed: %d", ret);
|
||||
spice.log(SPICE_SDK_LOG_LEVEL_WARNING, "sample_v0", log_message);
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_keypad() {
|
||||
spice.set_keypad(0, 0);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
LIBRARY sdk_sample_v0_flat_c
|
||||
|
||||
EXPORTS
|
||||
spice_sdk_entry_point
|
||||
@@ -0,0 +1,598 @@
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
|
||||
#include "sdk.h"
|
||||
#include "avs/game.h"
|
||||
#include "games/io.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "sdk/include/spicesdk.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
#include "acio/mdxf/mdxf_poll.h"
|
||||
|
||||
namespace sdk {
|
||||
|
||||
static spice_sdk_init_func sdk_init;
|
||||
static spice_sdk_log_func sdk_log;
|
||||
static spice_sdk_get_avs_info_func sdk_get_avs_info;
|
||||
static spice_sdk_get_game_info_func sdk_get_game_info;
|
||||
static spice_sdk_get_button_func sdk_get_button;
|
||||
static spice_sdk_set_button_func sdk_set_button;
|
||||
static spice_sdk_get_analog_func sdk_get_analog;
|
||||
static spice_sdk_set_analog_func sdk_set_analog;
|
||||
static spice_sdk_get_light_func sdk_get_light;
|
||||
static spice_sdk_set_light_func sdk_set_light;
|
||||
static spice_sdk_set_touch_func sdk_set_touch;
|
||||
static spice_sdk_clear_touch_func sdk_clear_touch;
|
||||
static spice_sdk_insert_card_func sdk_insert_card;
|
||||
static spice_sdk_set_keypad_func sdk_set_keypad;
|
||||
|
||||
struct SdkModule {
|
||||
std::string dll;
|
||||
HINSTANCE module;
|
||||
};
|
||||
|
||||
// DLLs
|
||||
static int sdk_modules_count = 0;
|
||||
static std::vector<SdkModule> sdk_modules_list;
|
||||
static std::shared_mutex sdk_global_mutex;
|
||||
|
||||
// internal
|
||||
static bool sdk_initialized = false;
|
||||
static bool sdk_shutting_down = false;
|
||||
static std::vector<Button> *buttons;
|
||||
static std::vector<Analog> *analogs;
|
||||
static std::vector<Light> *lights;
|
||||
|
||||
// callbacks
|
||||
static std::mutex sdk_callback_registration_mutex;
|
||||
static std::vector<spice_sdk_destroy_callback_func *> callbacks_destroy;
|
||||
|
||||
void register_sdk_hooks(std::string dll, HINSTANCE module) {
|
||||
sdk_modules_list.emplace_back(std::move(dll), module);
|
||||
sdk_modules_count += 1;
|
||||
}
|
||||
|
||||
void init_sdk_modules() {
|
||||
if (sdk_modules_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// prepare
|
||||
buttons = games::get_buttons(eamuse_get_game());
|
||||
analogs = games::get_analogs(eamuse_get_game());
|
||||
lights = games::get_lights(eamuse_get_game());
|
||||
|
||||
// under exclusive lock, mark the SDK helpers as being available
|
||||
{
|
||||
std::unique_lock lock(sdk_global_mutex);
|
||||
sdk_initialized = true;
|
||||
}
|
||||
|
||||
// without any locks, call into each DLL; this may call back into SDK functions
|
||||
for (auto &module : sdk_modules_list) {
|
||||
// get a pointer to dll's spice_sdk_entry_point
|
||||
const auto entry_point = reinterpret_cast<spice_sdk_entry_point_func *>(
|
||||
GetProcAddress(module.module, "spice_sdk_entry_point"));
|
||||
if (!entry_point) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// call into it on this thread
|
||||
log_info("sdk", "registering SDK hooks for {}", module.dll);
|
||||
const auto ret = entry_point(sdk_init);
|
||||
log_info("sdk", "spice_sdk_entry_point returned {}", ret);
|
||||
}
|
||||
}
|
||||
|
||||
void fini_sdk_modules() {
|
||||
// prevent multiple calls and further calls into sdk_init
|
||||
{
|
||||
std::unique_lock lock(sdk_global_mutex);
|
||||
if (!sdk_initialized) {
|
||||
return;
|
||||
}
|
||||
sdk_shutting_down = true;
|
||||
}
|
||||
|
||||
// call into destroy callback of each DLL
|
||||
// this may call back into SDK functions (e.g., for logging)
|
||||
// so we leave sdk_initialized as-is
|
||||
{
|
||||
log_info("sdk", "calling destroy callbacks...");
|
||||
std::lock_guard lock(sdk_callback_registration_mutex);
|
||||
for (const auto& destroy : callbacks_destroy) {
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// under exclusive lock, mark the SDK functions as no longer available
|
||||
{
|
||||
std::unique_lock lock(sdk_global_mutex);
|
||||
sdk_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_init(
|
||||
uint32_t version,
|
||||
spice_sdk_destroy_callback_func *destroy_callback,
|
||||
void *sdk_functions
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (sdk_shutting_down || !sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
uint32_t size;
|
||||
if (version != 0) {
|
||||
log_warning("sdk", "sdk_init returning NOT_SUPPORTED due to invalid version: {}", version);
|
||||
return SPICE_SDK_STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (!destroy_callback) {
|
||||
log_warning("sdk", "sdk_init returning INVALID_ARGUMENT_2 due to invalid destroy_callback pointer");
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
|
||||
}
|
||||
if (!sdk_functions) {
|
||||
log_warning("sdk", "sdk_init returning INVALID_ARGUMENT_3 due to invalid sdk_functions pointer");
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_3;
|
||||
}
|
||||
|
||||
auto *v0 = reinterpret_cast<SPICE_SDK_V0 *>(sdk_functions);
|
||||
if (v0->size < RTL_SIZEOF_THROUGH_FIELD(SPICE_SDK_V0, set_keypad)) {
|
||||
log_warning("sdk", "sdk_init returning TOO_SMALL due to size field of SPICE_SDK_V0 not being set");
|
||||
return SPICE_SDK_STATUS_TOO_SMALL;
|
||||
}
|
||||
|
||||
// we are trusting the size passed in by the caller
|
||||
size = v0->size;
|
||||
memset(v0, 0, size);
|
||||
v0->size = size;
|
||||
|
||||
v0->log = sdk_log;
|
||||
v0->get_game_info = sdk_get_game_info;
|
||||
v0->get_avs_info = sdk_get_avs_info;
|
||||
v0->get_button = sdk_get_button;
|
||||
v0->set_button = sdk_set_button;
|
||||
v0->get_analog = sdk_get_analog;
|
||||
v0->set_analog = sdk_set_analog;
|
||||
v0->get_light = sdk_get_light;
|
||||
v0->set_light = sdk_set_light;
|
||||
v0->set_touch = sdk_set_touch;
|
||||
v0->clear_touch = sdk_clear_touch;
|
||||
v0->insert_card = sdk_insert_card;
|
||||
v0->set_keypad = sdk_set_keypad;
|
||||
// end of 0.1
|
||||
// any newer minor iterations will need to check the size
|
||||
|
||||
{
|
||||
// destroy callbacks are only called upon successful return from this routine
|
||||
std::lock_guard lock(sdk_callback_registration_mutex);
|
||||
callbacks_destroy.push_back(destroy_callback);
|
||||
}
|
||||
|
||||
log_info("sdk", "sdk_init returning SUCCESS");
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_log(
|
||||
SPICE_SDK_LOG_LEVEL level,
|
||||
const char *facility,
|
||||
const char *message
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (!sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
if (facility == nullptr) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
|
||||
}
|
||||
|
||||
const std::string facility_str = fmt::format("sdk::{}", facility);
|
||||
|
||||
switch (level) {
|
||||
case SPICE_SDK_LOG_LEVEL_MISC:
|
||||
log_misc(facility_str.c_str(), "{}", message);
|
||||
break;
|
||||
case SPICE_SDK_LOG_LEVEL_INFO:
|
||||
log_info(facility_str.c_str(), "{}", message);
|
||||
break;
|
||||
case SPICE_SDK_LOG_LEVEL_WARNING:
|
||||
log_warning(facility_str.c_str(), "{}", message);
|
||||
break;
|
||||
case SPICE_SDK_LOG_LEVEL_FATAL:
|
||||
log_fatal(facility_str.c_str(), "{}", message);
|
||||
break;
|
||||
default:
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||
}
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_get_avs_info(
|
||||
SPICE_SDK_AVS_INFO *info
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (!sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
if (!info) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||
}
|
||||
// MDXJAA2025061002
|
||||
strncpy(info->model, avs::game::MODEL, sizeof(info->model));
|
||||
info->dest = avs::game::DEST[0];
|
||||
info->spec = avs::game::SPEC[0];
|
||||
info->rev = avs::game::REV[0];
|
||||
strncpy(info->ext, avs::game::EXT, sizeof(info->ext));
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_get_game_info(
|
||||
SPICE_SDK_GAME_INFO *info
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (!sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
if (!info) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||
}
|
||||
|
||||
*info = {};
|
||||
|
||||
const std::string game_name = eamuse_get_game();
|
||||
strncpy(info->name, game_name.c_str(), sizeof(info->name));
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_get_button (
|
||||
uint32_t button_id,
|
||||
bool *pressed,
|
||||
float *velocity
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (!sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
if (!buttons) {
|
||||
return SPICE_SDK_STATUS_NOT_INITIALIZED;
|
||||
}
|
||||
if (button_id >= buttons->size()) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||
}
|
||||
|
||||
Button &button = (*buttons)[button_id];
|
||||
if (pressed) {
|
||||
*pressed = (GameAPI::Buttons::getState(RI_MGR, button) == GameAPI::Buttons::BUTTON_PRESSED);
|
||||
}
|
||||
if (velocity) {
|
||||
*velocity = GameAPI::Buttons::getVelocity(RI_MGR, button);
|
||||
}
|
||||
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_set_button (
|
||||
uint32_t button_id,
|
||||
bool pressed,
|
||||
float velocity
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (!sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
if (!buttons) {
|
||||
return SPICE_SDK_STATUS_NOT_INITIALIZED;
|
||||
}
|
||||
if (button_id >= buttons->size()) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||
}
|
||||
if (pressed) {
|
||||
velocity = std::clamp(velocity, 0.f, 1.f);
|
||||
}
|
||||
|
||||
Button &button = (*buttons)[button_id];
|
||||
if (pressed) {
|
||||
button.override_state = pressed ? GameAPI::Buttons::BUTTON_PRESSED : GameAPI::Buttons::BUTTON_NOT_PRESSED;
|
||||
button.override_velocity = velocity;
|
||||
}
|
||||
button.override_enabled = pressed;
|
||||
mdxf_poll(true);
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_get_analog (
|
||||
uint32_t analog_id,
|
||||
float *value
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (!sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
if (!analogs) {
|
||||
return SPICE_SDK_STATUS_NOT_INITIALIZED;
|
||||
}
|
||||
if (analog_id >= analogs->size()) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||
}
|
||||
if (!value) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
|
||||
}
|
||||
|
||||
Analog &analog = (*analogs)[analog_id];
|
||||
*value = GameAPI::Analogs::getState(RI_MGR, analog);
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_set_analog (
|
||||
uint32_t analog_id,
|
||||
bool override_active,
|
||||
float value
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (!sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
if (!analogs) {
|
||||
return SPICE_SDK_STATUS_NOT_INITIALIZED;
|
||||
}
|
||||
if (analog_id >= analogs->size()) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||
}
|
||||
if (override_active) {
|
||||
value = std::clamp(value, 0.f, 1.f);
|
||||
}
|
||||
|
||||
Analog &analog = (*analogs)[analog_id];
|
||||
if (override_active) {
|
||||
analog.override_state = value;
|
||||
}
|
||||
analog.override_enabled = override_active;
|
||||
mdxf_poll(true);
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_get_light(
|
||||
uint32_t light_id,
|
||||
float *value
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (!sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
if (!lights) {
|
||||
return SPICE_SDK_STATUS_NOT_INITIALIZED;
|
||||
}
|
||||
if (light_id >= lights->size()) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||
}
|
||||
if (!value) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
|
||||
}
|
||||
Light &light = (*lights)[light_id];
|
||||
*value = GameAPI::Lights::readLight(RI_MGR, light);
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_set_light(
|
||||
uint32_t light_id,
|
||||
bool override_active,
|
||||
float value
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (!sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
if (!lights) {
|
||||
return SPICE_SDK_STATUS_NOT_INITIALIZED;
|
||||
}
|
||||
if (light_id >= lights->size()) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||
}
|
||||
if (override_active) {
|
||||
value = std::clamp(value, 0.f, 1.f);
|
||||
}
|
||||
|
||||
Light &light = (*lights)[light_id];
|
||||
if (override_active) {
|
||||
light.override_state = value;
|
||||
}
|
||||
light.override_enabled = override_active;
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_set_touch(
|
||||
const SPICE_SDK_TOUCH_POINT *points,
|
||||
uint32_t count
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (!sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
if (count == 0 || !points) {
|
||||
return SPICE_SDK_STATUS_TOO_SMALL;
|
||||
}
|
||||
|
||||
std::vector<TouchPoint> touch_points;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
const SPICE_SDK_TOUCH_POINT &point = points[i];
|
||||
touch_points.emplace_back(TouchPoint {
|
||||
.id = point.id,
|
||||
.x = point.x,
|
||||
.y = point.y,
|
||||
.mouse = false,
|
||||
});
|
||||
}
|
||||
touch_write_points(&touch_points);
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_clear_touch(
|
||||
const uint32_t *ids,
|
||||
uint32_t count
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (!sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
if (count == 0 || !ids) {
|
||||
return SPICE_SDK_STATUS_TOO_SMALL;
|
||||
}
|
||||
|
||||
std::vector<DWORD> touch_point_ids;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
touch_point_ids.push_back(ids[i]);
|
||||
}
|
||||
touch_remove_points(&touch_point_ids);
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_insert_card(
|
||||
uint8_t unit,
|
||||
const char *card_id
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (!sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
if (unit >= 2) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||
}
|
||||
|
||||
if (!card_id) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
|
||||
}
|
||||
if (strlen(card_id) != 16) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
|
||||
}
|
||||
|
||||
// convert to binary
|
||||
uint8_t card_bin[8] {};
|
||||
if (!hex2bin(card_id, card_bin)) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
|
||||
}
|
||||
|
||||
// insert
|
||||
eamuse_card_insert(unit & 1, card_bin);
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
struct KeypadMapping {
|
||||
char character;
|
||||
uint16_t state;
|
||||
};
|
||||
|
||||
static KeypadMapping KEYPAD_MAPPINGS[] = {
|
||||
{ '0', 1 << EAM_IO_KEYPAD_0 },
|
||||
{ '1', 1 << EAM_IO_KEYPAD_1 },
|
||||
{ '2', 1 << EAM_IO_KEYPAD_2 },
|
||||
{ '3', 1 << EAM_IO_KEYPAD_3 },
|
||||
{ '4', 1 << EAM_IO_KEYPAD_4 },
|
||||
{ '5', 1 << EAM_IO_KEYPAD_5 },
|
||||
{ '6', 1 << EAM_IO_KEYPAD_6 },
|
||||
{ '7', 1 << EAM_IO_KEYPAD_7 },
|
||||
{ '8', 1 << EAM_IO_KEYPAD_8 },
|
||||
{ '9', 1 << EAM_IO_KEYPAD_9 },
|
||||
{ 'A', 1 << EAM_IO_KEYPAD_00 },
|
||||
{ 'D', 1 << EAM_IO_KEYPAD_DECIMAL },
|
||||
};
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_set_keypad(
|
||||
uint8_t unit,
|
||||
char key
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (!sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
if (unit >= 2) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||
}
|
||||
|
||||
if (key == 0) {
|
||||
eamuse_set_keypad_overrides(unit, 0);
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// find mapping
|
||||
uint16_t state = 0;
|
||||
bool mapping_found = false;
|
||||
for (auto &mapping : KEYPAD_MAPPINGS) {
|
||||
if (mapping.character == key) {
|
||||
state |= mapping.state;
|
||||
mapping_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check for error
|
||||
if (!mapping_found) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
|
||||
}
|
||||
|
||||
// set
|
||||
eamuse_set_keypad_overrides(unit, state);
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
} // namespace sdk
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <windows.h>
|
||||
|
||||
namespace sdk {
|
||||
|
||||
void register_sdk_hooks(std::string dll, HINSTANCE module);
|
||||
void init_sdk_modules();
|
||||
void fini_sdk_modules();
|
||||
|
||||
}
|
||||
+66
-49
@@ -67,6 +67,8 @@ static const char *LOG_MODULE_NAME = "touch";
|
||||
|
||||
static TouchHandler *TOUCH_HANDLER = nullptr;
|
||||
|
||||
static bool is_mouse_message_from_touchscreen();
|
||||
|
||||
TouchHandler::TouchHandler(std::string name) {
|
||||
log_info("touch", "Using touch handler: {}", name);
|
||||
}
|
||||
@@ -215,6 +217,30 @@ void update_card_button() {
|
||||
}
|
||||
}
|
||||
|
||||
static void release_all_mouse_touch_points() {
|
||||
std::lock_guard<std::mutex> lock_points(TOUCH_POINTS_M);
|
||||
std::lock_guard<std::mutex> lock_events(TOUCH_EVENTS_M);
|
||||
|
||||
for (size_t x = 0; x < TOUCH_POINTS.size();) {
|
||||
TouchPoint *tp = &TOUCH_POINTS[x];
|
||||
|
||||
if (tp->id == 0u) {
|
||||
TouchEvent te {
|
||||
.id = tp->id,
|
||||
.x = tp->x,
|
||||
.y = tp->y,
|
||||
.type = TOUCH_UP,
|
||||
.mouse = tp->mouse,
|
||||
};
|
||||
add_touch_event(&te);
|
||||
|
||||
TOUCH_POINTS.erase(TOUCH_POINTS.begin() + x);
|
||||
} else {
|
||||
x++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
|
||||
// check if touch was registered
|
||||
@@ -490,35 +516,22 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
|
||||
// parse mouse messages
|
||||
switch (msg) {
|
||||
case WM_LBUTTONDOWN: {
|
||||
|
||||
// check if mouse is enabled
|
||||
if (SPICETOUCH_ENABLE_MOUSE) {
|
||||
if (is_mouse_message_from_touchscreen()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// subscribe to mouse messages even when the cursor leaves the window
|
||||
SetCapture(hWnd);
|
||||
|
||||
// release all old events before inserting a new one
|
||||
release_all_mouse_touch_points();
|
||||
|
||||
// lock touch points
|
||||
std::lock_guard<std::mutex> lock_points(TOUCH_POINTS_M);
|
||||
std::lock_guard<std::mutex> lock_events(TOUCH_EVENTS_M);
|
||||
|
||||
// remove all points with ID 0
|
||||
for (size_t x = 0; x < TOUCH_POINTS.size(); x++) {
|
||||
TouchPoint *tp = &TOUCH_POINTS[x];
|
||||
|
||||
if (tp->id == 0u) {
|
||||
|
||||
// generate touch up event
|
||||
TouchEvent te {
|
||||
.id = tp->id,
|
||||
.x = tp->x,
|
||||
.y = tp->y,
|
||||
.type = TOUCH_UP,
|
||||
.mouse = tp->mouse,
|
||||
};
|
||||
add_touch_event(&te);
|
||||
|
||||
// erase touch point
|
||||
TOUCH_POINTS.erase(TOUCH_POINTS.begin() + x);
|
||||
}
|
||||
}
|
||||
|
||||
// create touch point
|
||||
TouchPoint tp {
|
||||
.id = 0,
|
||||
@@ -545,9 +558,11 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
|
||||
break;
|
||||
}
|
||||
case WM_MOUSEMOVE: {
|
||||
|
||||
// check if mouse is enabled
|
||||
if (SPICETOUCH_ENABLE_MOUSE) {
|
||||
if (is_mouse_message_from_touchscreen()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// lock touch points
|
||||
std::lock_guard<std::mutex> lock_points(TOUCH_POINTS_M);
|
||||
@@ -582,40 +597,31 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
|
||||
break;
|
||||
}
|
||||
case WM_LBUTTONUP: {
|
||||
|
||||
// check if mouse is enabled
|
||||
if (SPICETOUCH_ENABLE_MOUSE) {
|
||||
if (is_mouse_message_from_touchscreen()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// lock touch points
|
||||
std::lock_guard<std::mutex> lock_points(TOUCH_POINTS_M);
|
||||
std::lock_guard<std::mutex> lock_events(TOUCH_EVENTS_M);
|
||||
|
||||
// remove all points with ID 0
|
||||
for (size_t x = 0; x < TOUCH_POINTS.size(); x++) {
|
||||
TouchPoint *tp = &TOUCH_POINTS[x];
|
||||
|
||||
if (tp->id == 0u) {
|
||||
|
||||
// generate touch up event
|
||||
TouchEvent te {
|
||||
.id = tp->id,
|
||||
.x = tp->x,
|
||||
.y = tp->y,
|
||||
.type = TOUCH_UP,
|
||||
.mouse = tp->mouse,
|
||||
};
|
||||
add_touch_event(&te);
|
||||
|
||||
// remove touch point
|
||||
TOUCH_POINTS.erase(TOUCH_POINTS.begin() + x);
|
||||
}
|
||||
release_all_mouse_touch_points();
|
||||
if (GetCapture() == hWnd) {
|
||||
ReleaseCapture();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case WM_CAPTURECHANGED:
|
||||
case WM_CANCELMODE: {
|
||||
// to deal with window losing the capture after SetCapture
|
||||
release_all_mouse_touch_points();
|
||||
if (msg == WM_CANCELMODE && GetCapture() == hWnd) {
|
||||
ReleaseCapture();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// call original function
|
||||
if (SPICETOUCH_CALL_OLD_PROC && SPICETOUCH_OLD_PROC != nullptr) {
|
||||
return SPICETOUCH_OLD_PROC(hWnd, msg, wParam, lParam);
|
||||
@@ -663,6 +669,8 @@ void touch_attach_dx_hook() {
|
||||
// initialize touch handler
|
||||
touch_initialize();
|
||||
|
||||
log_info("touch", "touch_attach_dx_hook: attaching SpiceTouchWndProc...");
|
||||
|
||||
// add dx hook
|
||||
graphics_add_wnd_proc(SpiceTouchWndProc);
|
||||
|
||||
@@ -681,6 +689,8 @@ void touch_create_wnd(HWND hWnd, bool overlay) {
|
||||
// initialize touch handler
|
||||
touch_initialize();
|
||||
|
||||
log_info("touch", "touch_create_wnd: creating SPICETOUCH_TOUCH_THREAD...");
|
||||
|
||||
// create thread
|
||||
SPICETOUCH_TOUCH_THREAD = new std::thread([hWnd, overlay]() {
|
||||
|
||||
@@ -956,4 +966,11 @@ void update_spicetouch_window_dimensions(HWND hWnd) {
|
||||
SPICETOUCH_TOUCH_Y = topleft.y;
|
||||
SPICETOUCH_TOUCH_WIDTH = bottomright.x - topleft.x;
|
||||
SPICETOUCH_TOUCH_HEIGHT = bottomright.y - topleft.y;
|
||||
}
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages
|
||||
static bool is_mouse_message_from_touchscreen() {
|
||||
constexpr ULONG_PTR MI_WP_SIGNATURE = 0xFF515700;
|
||||
constexpr ULONG_PTR SIGNATURE_MASK = 0xFFFFFF00;
|
||||
return (GetMessageExtraInfo() & SIGNATURE_MASK) == MI_WP_SIGNATURE;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
#define _WIN32_WINNT 0x0602
|
||||
|
||||
#include "precise_timer.h"
|
||||
|
||||
#define WIN32_NO_STATUS
|
||||
#include <windows.h>
|
||||
#undef WIN32_NO_STATUS
|
||||
|
||||
#include <timeapi.h>
|
||||
|
||||
#if !SPICE_XP
|
||||
#include <processthreadsapi.h>
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include "util/logging.h"
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
#define PERIOD 1
|
||||
#define TOLERANCE 0.02
|
||||
|
||||
#ifndef PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION
|
||||
#define PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION 0x4
|
||||
#endif
|
||||
|
||||
namespace timeutils {
|
||||
|
||||
bool TIMER_HACKS_DISABLE = false;
|
||||
|
||||
static TimerHandle get_precise_sleep_timer();
|
||||
static void destroy_precise_sleep_timer(TimerHandle timer);
|
||||
static void precise_sleep(TimerHandle timer, double seconds);
|
||||
|
||||
void set_timer_resolution() {
|
||||
if (TIMER_HACKS_DISABLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if !SPICE_XP
|
||||
|
||||
// make a call to opt out of power throttling
|
||||
// (requested timer resolution being ignored when window is occluded or minimized)
|
||||
// SetProcessInformation is win8+
|
||||
const auto kernel32 = GetModuleHandleA("kernel32.dll");
|
||||
if (kernel32) {
|
||||
const auto SetProcessInformation_addr =
|
||||
reinterpret_cast<decltype(SetProcessInformation) *>(
|
||||
GetProcAddress(kernel32, "SetProcessInformation"));
|
||||
|
||||
if (SetProcessInformation_addr) {
|
||||
PROCESS_POWER_THROTTLING_STATE state {};
|
||||
state.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
|
||||
state.ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION;
|
||||
state.StateMask = 0;
|
||||
const auto ret = SetProcessInformation_addr(
|
||||
GetCurrentProcess(),
|
||||
ProcessPowerThrottling,
|
||||
&state,
|
||||
sizeof(state));
|
||||
|
||||
log_info(
|
||||
"timeutils",
|
||||
"SetProcessInformation called to disable timer resolution throttling, returned {}",
|
||||
ret);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
const auto ret = timeBeginPeriod(PERIOD);
|
||||
log_info(
|
||||
"timeutils",
|
||||
"timeBeginPeriod({}) called, returned {}",
|
||||
PERIOD,
|
||||
ret);
|
||||
// and then we never call timeEndPeriod();
|
||||
// the game will also call this but the OS will honor the lowest value (1 above)
|
||||
}
|
||||
|
||||
#if !SPICE_XP
|
||||
|
||||
// timerSleep from https://blog.bearcats.nl/perfect-sleep-function/
|
||||
static void timerSleep(HANDLE timer, double seconds) {
|
||||
using namespace std::chrono;
|
||||
|
||||
auto t = high_resolution_clock::now();
|
||||
const auto target = t + nanoseconds(int64_t(seconds * 1e9));
|
||||
const int64_t maxTicks = PERIOD * 9'500;
|
||||
for (;;) {
|
||||
const int64_t remaining = (target - t).count();
|
||||
int64_t ticks = (remaining - TOLERANCE) / 100;
|
||||
if (ticks <= 0) {
|
||||
break;
|
||||
}
|
||||
if (ticks > maxTicks) {
|
||||
ticks = maxTicks;
|
||||
}
|
||||
|
||||
LARGE_INTEGER due;
|
||||
due.QuadPart = -ticks;
|
||||
SetWaitableTimerEx(timer, &due, 0, NULL, NULL, NULL, 0);
|
||||
WaitForSingleObject(timer, INFINITE);
|
||||
t = high_resolution_clock::now();
|
||||
}
|
||||
|
||||
while (high_resolution_clock::now() < target) {
|
||||
YieldProcessor();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static TimerHandle get_precise_sleep_timer() {
|
||||
TimerHandle timer = nullptr;
|
||||
if (TIMER_HACKS_DISABLE) {
|
||||
return timer;
|
||||
}
|
||||
|
||||
// CreateWaitableTimerExW is Vista+
|
||||
// CREATE_WAITABLE_TIMER_HIGH_RESOLUTION win10 1803+
|
||||
|
||||
#if !SPICE_XP
|
||||
|
||||
timer = CreateWaitableTimerExW(
|
||||
NULL,
|
||||
NULL,
|
||||
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
|
||||
TIMER_ALL_ACCESS);
|
||||
|
||||
#endif
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
static void destroy_precise_sleep_timer(TimerHandle timer) {
|
||||
if (timer) {
|
||||
CloseHandle(timer);
|
||||
}
|
||||
}
|
||||
|
||||
static void precise_sleep(TimerHandle timer, double seconds) {
|
||||
if (!std::isfinite(seconds) || seconds <= 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if !SPICE_XP
|
||||
if (timer) {
|
||||
timerSleep(timer, seconds);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// robustSleep is too CPU heavy so we will stick to Sleep() with
|
||||
// timeBeginPeriod, which has an error at most 1ms
|
||||
const auto milliseconds = std::ceil(seconds * 1000.0);
|
||||
if (milliseconds >= std::numeric_limits<DWORD>::max()) {
|
||||
Sleep(std::numeric_limits<DWORD>::max());
|
||||
return;
|
||||
}
|
||||
|
||||
Sleep(static_cast<DWORD>(milliseconds));
|
||||
}
|
||||
|
||||
// RAII wrapper
|
||||
|
||||
PreciseSleepTimer::PreciseSleepTimer() : timer(get_precise_sleep_timer()) {}
|
||||
|
||||
PreciseSleepTimer::~PreciseSleepTimer() {
|
||||
destroy_precise_sleep_timer(timer);
|
||||
}
|
||||
|
||||
void PreciseSleepTimer::sleep(uint32_t ms) const {
|
||||
sleep(std::chrono::milliseconds(ms));
|
||||
}
|
||||
|
||||
void PreciseSleepTimer::sleep_seconds(double seconds) const {
|
||||
precise_sleep(timer, seconds);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
namespace timeutils {
|
||||
|
||||
extern bool TIMER_HACKS_DISABLE;
|
||||
|
||||
void set_timer_resolution();
|
||||
|
||||
using TimerHandle = void *;
|
||||
|
||||
class PreciseSleepTimer {
|
||||
public:
|
||||
PreciseSleepTimer();
|
||||
~PreciseSleepTimer();
|
||||
|
||||
PreciseSleepTimer(const PreciseSleepTimer &) = delete;
|
||||
PreciseSleepTimer &operator=(const PreciseSleepTimer &) = delete;
|
||||
PreciseSleepTimer(PreciseSleepTimer &&) = delete;
|
||||
PreciseSleepTimer &operator=(PreciseSleepTimer &&) = delete;
|
||||
|
||||
void sleep(uint32_t ms) const;
|
||||
|
||||
template <typename Rep, typename Period>
|
||||
void sleep(std::chrono::duration<Rep, Period> duration) const {
|
||||
sleep_seconds(std::chrono::duration<double>(duration).count());
|
||||
}
|
||||
|
||||
private:
|
||||
void sleep_seconds(double seconds) const;
|
||||
|
||||
TimerHandle timer;
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -319,14 +319,14 @@ bool acquire_shutdown_privs();
|
||||
|
||||
void generate_ea_card(char card[17]);
|
||||
|
||||
static inline int get_async_primary_mouse() {
|
||||
static inline bool get_async_primary_mouse() {
|
||||
int vk = GetSystemMetrics(SM_SWAPBUTTON) ? VK_RBUTTON : VK_LBUTTON;
|
||||
return GetAsyncKeyState(vk);
|
||||
return (GetAsyncKeyState(vk) & 0x8000) != 0;
|
||||
}
|
||||
|
||||
static inline int get_async_secondary_mouse() {
|
||||
static inline bool get_async_secondary_mouse() {
|
||||
int vk = GetSystemMetrics(SM_SWAPBUTTON) ? VK_LBUTTON : VK_RBUTTON;
|
||||
return GetAsyncKeyState(vk);
|
||||
return (GetAsyncKeyState(vk) & 0x8000) != 0;
|
||||
}
|
||||
|
||||
static inline bool parse_width_height(const std::string wh, std::pair<uint32_t, uint32_t> &result) {
|
||||
|
||||
Reference in New Issue
Block a user