mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c590b87cff | |||
| 5cabd026ae | |||
| 7ccd938f9b | |||
| 88a210e82e | |||
| 74e619df37 | |||
| 10a97b9c63 | |||
| 159043803c | |||
| bd2fbfcb67 | |||
| 64600826b7 | |||
| 80deb4bbbd | |||
| 00c8bb8ce6 | |||
| 09ed8b948c | |||
| e2daefbb6a | |||
| 39bf17aafe | |||
| 4836cd94ab | |||
| 9d41ca2515 | |||
| 18421ffbfe | |||
| 3f0921d983 | |||
| ed7318c270 | |||
| 623e1e3998 | |||
| 558e3d0cb3 | |||
| d0db547dd0 | |||
| be4bb00390 | |||
| a8cc85531c | |||
| 2dea0d27bf | |||
| 61a0220c1a | |||
| c080bbe301 | |||
| e1d1b39567 |
@@ -411,12 +411,14 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
games/sdvx/sdvx_live2d.cpp
|
games/sdvx/sdvx_live2d.cpp
|
||||||
games/sdvx/io.cpp
|
games/sdvx/io.cpp
|
||||||
games/sdvx/camera.cpp
|
games/sdvx/camera.cpp
|
||||||
|
games/jb/bi2x_hook.cpp
|
||||||
games/jb/jb.cpp
|
games/jb/jb.cpp
|
||||||
games/jb/jb_touch.cpp
|
games/jb/jb_touch.cpp
|
||||||
games/jb/io.cpp
|
games/jb/io.cpp
|
||||||
games/nost/nost.cpp
|
games/nost/nost.cpp
|
||||||
games/nost/io.cpp
|
games/nost/io.cpp
|
||||||
games/nost/poke.cpp
|
games/nost/poke.cpp
|
||||||
|
games/nost/touch_mode.cpp
|
||||||
games/gitadora/gitadora.cpp
|
games/gitadora/gitadora.cpp
|
||||||
games/gitadora/asio.cpp
|
games/gitadora/asio.cpp
|
||||||
games/gitadora/io.cpp
|
games/gitadora/io.cpp
|
||||||
@@ -502,6 +504,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
hooks/audio/buffer.cpp
|
hooks/audio/buffer.cpp
|
||||||
hooks/audio/mme.cpp
|
hooks/audio/mme.cpp
|
||||||
hooks/audio/util.cpp
|
hooks/audio/util.cpp
|
||||||
|
hooks/audio/xact.cpp
|
||||||
hooks/audio/backends/dsound/dsound_backend.cpp
|
hooks/audio/backends/dsound/dsound_backend.cpp
|
||||||
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
|
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
|
||||||
hooks/audio/backends/mmdevice/device.cpp
|
hooks/audio/backends/mmdevice/device.cpp
|
||||||
@@ -528,6 +531,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
hooks/devicehook.cpp
|
hooks/devicehook.cpp
|
||||||
hooks/graphics/graphics.cpp
|
hooks/graphics/graphics.cpp
|
||||||
hooks/graphics/graphics_windowed.cpp
|
hooks/graphics/graphics_windowed.cpp
|
||||||
|
hooks/graphics/nvapi_impl.cpp
|
||||||
hooks/graphics/nvapi_hook.cpp
|
hooks/graphics/nvapi_hook.cpp
|
||||||
hooks/graphics/nvenc_hook.cpp
|
hooks/graphics/nvenc_hook.cpp
|
||||||
hooks/graphics/backends/d3d9/d3d9_backend.cpp
|
hooks/graphics/backends/d3d9/d3d9_backend.cpp
|
||||||
@@ -571,7 +575,6 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
misc/device.cpp
|
misc/device.cpp
|
||||||
misc/eamuse.cpp
|
misc/eamuse.cpp
|
||||||
misc/extdev.cpp
|
misc/extdev.cpp
|
||||||
misc/nativetouchhook.cpp
|
|
||||||
misc/sciunit.cpp
|
misc/sciunit.cpp
|
||||||
misc/sde.cpp
|
misc/sde.cpp
|
||||||
misc/wintouchemu.cpp
|
misc/wintouchemu.cpp
|
||||||
@@ -609,17 +612,26 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
overlay/windows/keypad.cpp
|
overlay/windows/keypad.cpp
|
||||||
overlay/windows/log.cpp
|
overlay/windows/log.cpp
|
||||||
overlay/windows/midi.cpp
|
overlay/windows/midi.cpp
|
||||||
|
overlay/windows/nostalgia_touch_piano.cpp
|
||||||
overlay/windows/obs.cpp
|
overlay/windows/obs.cpp
|
||||||
overlay/windows/obs_websocket.cpp
|
overlay/windows/obs_websocket.cpp
|
||||||
overlay/windows/patch_manager.cpp
|
overlay/windows/patch_manager.cpp
|
||||||
overlay/windows/popn_sub.cpp
|
overlay/windows/popn_sub.cpp
|
||||||
overlay/windows/wnd_manager.cpp
|
overlay/windows/wnd_manager.cpp
|
||||||
|
|
||||||
|
# patcher
|
||||||
|
patcher/config.cpp
|
||||||
|
patcher/lifecycle.cpp
|
||||||
|
patcher/loader.cpp
|
||||||
|
patcher/loader_group.cpp
|
||||||
|
patcher/runtime.cpp
|
||||||
|
|
||||||
# external
|
# external
|
||||||
external/easywsclient/easywsclient.cpp
|
external/easywsclient/easywsclient.cpp
|
||||||
|
|
||||||
# rawinput
|
# rawinput
|
||||||
rawinput/rawinput.cpp
|
rawinput/rawinput.cpp
|
||||||
|
rawinput/rawinput_handles.cpp
|
||||||
rawinput/midi.cpp
|
rawinput/midi.cpp
|
||||||
rawinput/sextet.cpp
|
rawinput/sextet.cpp
|
||||||
rawinput/piuio.cpp
|
rawinput/piuio.cpp
|
||||||
@@ -648,6 +660,11 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
|
|
||||||
# touch
|
# touch
|
||||||
touch/gdi_overlay.cpp
|
touch/gdi_overlay.cpp
|
||||||
|
touch/native/inject.cpp
|
||||||
|
touch/native/inject_mouse.cpp
|
||||||
|
touch/native/inject_synthetic.cpp
|
||||||
|
touch/native/nativetouchhook.cpp
|
||||||
|
touch/native/transform.cpp
|
||||||
touch/touch.cpp
|
touch/touch.cpp
|
||||||
touch/touch_gestures.cpp
|
touch/touch_gestures.cpp
|
||||||
touch/win7.cpp
|
touch/win7.cpp
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "launcher/launcher.h"
|
#include "launcher/launcher.h"
|
||||||
#include "misc/wintouchemu.h"
|
#include "misc/wintouchemu.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
|
#include "touch/native/nativetouchhook.h"
|
||||||
|
|
||||||
// static stuff
|
// static stuff
|
||||||
static int ACIO_WARMUP = 0;
|
static int ACIO_WARMUP = 0;
|
||||||
@@ -140,8 +141,9 @@ static int __cdecl ac_io_update(int a1) {
|
|||||||
// flush device output
|
// flush device output
|
||||||
RI_MGR->devices_flush_output();
|
RI_MGR->devices_flush_output();
|
||||||
|
|
||||||
// update wintouchemu
|
// update touch emulation
|
||||||
wintouchemu::update();
|
wintouchemu::update();
|
||||||
|
nativetouch::refresh_contact_lifetime();
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
#include "games/nost/io.h"
|
#include "games/nost/io.h"
|
||||||
#include "games/nost/nost.h"
|
#include "games/nost/nost.h"
|
||||||
|
#include "games/nost/touch_mode.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ using namespace GameAPI;
|
|||||||
// static stuff
|
// static stuff
|
||||||
static uint8_t STATUS_BUFFER[277];
|
static uint8_t STATUS_BUFFER[277];
|
||||||
static bool STATUS_BUFFER_FREEZE = false;
|
static bool STATUS_BUFFER_FREEZE = false;
|
||||||
|
static constexpr uint8_t NOST_TOUCH_PIANO_VELOCITY = 11;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Implementations
|
* Implementations
|
||||||
@@ -226,6 +228,8 @@ static bool __cdecl ac_io_panb_update_control_status_buffer() {
|
|||||||
games::nost::Analogs::Key27, games::nost::Analogs::Key28,
|
games::nost::Analogs::Key27, games::nost::Analogs::Key28,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const auto touch_key_state = games::nost::touch_mode::piano_key_state();
|
||||||
|
|
||||||
// iterate pairs of keys
|
// iterate pairs of keys
|
||||||
for (size_t key_pair = 0; key_pair < 28 / 2; key_pair++) {
|
for (size_t key_pair = 0; key_pair < 28 / 2; key_pair++) {
|
||||||
|
|
||||||
@@ -257,6 +261,10 @@ static bool __cdecl ac_io_panb_update_control_status_buffer() {
|
|||||||
if (button0 > 0) {
|
if (button0 > 0) {
|
||||||
state0 = button0;
|
state0 = button0;
|
||||||
}
|
}
|
||||||
|
if ((touch_key_state & (UINT32_C(1) << (key_pair * 2))) &&
|
||||||
|
state0 < NOST_TOUCH_PIANO_VELOCITY) {
|
||||||
|
state0 = NOST_TOUCH_PIANO_VELOCITY;
|
||||||
|
}
|
||||||
|
|
||||||
const auto button1 = panb_get_button_velocity(
|
const auto button1 = panb_get_button_velocity(
|
||||||
buttons.at(button_mapping[key_pair * 2 + 1]),
|
buttons.at(button_mapping[key_pair * 2 + 1]),
|
||||||
@@ -267,6 +275,10 @@ static bool __cdecl ac_io_panb_update_control_status_buffer() {
|
|||||||
if (button1 > 0) {
|
if (button1 > 0) {
|
||||||
state1 = button1;
|
state1 = button1;
|
||||||
}
|
}
|
||||||
|
if ((touch_key_state & (UINT32_C(1) << (key_pair * 2 + 1))) &&
|
||||||
|
state1 < NOST_TOUCH_PIANO_VELOCITY) {
|
||||||
|
state1 = NOST_TOUCH_PIANO_VELOCITY;
|
||||||
|
}
|
||||||
|
|
||||||
// build value
|
// build value
|
||||||
uint8_t value = 0;
|
uint8_t value = 0;
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
|
|||||||
// SDVX Old cabinet mode
|
// SDVX Old cabinet mode
|
||||||
if (avs::game::is_model("KFC") && avs::game::SPEC[0] != 'G' && avs::game::SPEC[0] != 'H')
|
if (avs::game::is_model("KFC") && avs::game::SPEC[0] != 'G' && avs::game::SPEC[0] != 'H')
|
||||||
answer_type = 1;
|
answer_type = 1;
|
||||||
if (avs::game::is_model("L44"))
|
if (avs::game::is_model({ "L44", "T44" }))
|
||||||
answer_type = 2;
|
answer_type = 2;
|
||||||
|
|
||||||
// check answer type
|
// check answer type
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
#include "keypads.h"
|
#include "keypads.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
#include "external/rapidjson/document.h"
|
#include "external/rapidjson/document.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "util/precise_timer.h"
|
|
||||||
|
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
using namespace rapidjson;
|
using namespace rapidjson;
|
||||||
@@ -59,7 +60,6 @@ namespace api::modules {
|
|||||||
// get params
|
// get params
|
||||||
auto keypad = req.params[0].GetUint();
|
auto keypad = req.params[0].GetUint();
|
||||||
auto input = std::string(req.params[1].GetString());
|
auto input = std::string(req.params[1].GetString());
|
||||||
timeutils::PreciseSleepTimer timer;
|
|
||||||
|
|
||||||
// process all chars
|
// process all chars
|
||||||
for (auto c : input) {
|
for (auto c : input) {
|
||||||
@@ -93,11 +93,11 @@ namespace api::modules {
|
|||||||
|
|
||||||
// set
|
// set
|
||||||
eamuse_set_keypad_overrides(keypad, state);
|
eamuse_set_keypad_overrides(keypad, state);
|
||||||
timer.sleep(sleep_time);
|
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
|
||||||
|
|
||||||
// unset
|
// unset
|
||||||
eamuse_set_keypad_overrides(keypad, 0);
|
eamuse_set_keypad_overrides(keypad, 0);
|
||||||
timer.sleep(sleep_time);
|
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "touch.h"
|
#include "touch.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include "external/rapidjson/document.h"
|
#include "external/rapidjson/document.h"
|
||||||
@@ -8,6 +10,8 @@
|
|||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "launcher/launcher.h"
|
#include "launcher/launcher.h"
|
||||||
#include "touch/touch.h"
|
#include "touch/touch.h"
|
||||||
|
#include "touch/native/inject.h"
|
||||||
|
#include "touch/native/nativetouchhook.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "games/iidx/iidx.h"
|
#include "games/iidx/iidx.h"
|
||||||
|
|
||||||
@@ -17,6 +21,25 @@ using namespace rapidjson;
|
|||||||
|
|
||||||
namespace api::modules {
|
namespace api::modules {
|
||||||
|
|
||||||
|
static bool inject_native_touch(
|
||||||
|
const std::vector<TouchPoint> &touch_points, int canvas_w, int canvas_h) {
|
||||||
|
if (touch_points.empty()) {
|
||||||
|
// no active touches remain; release the synthetic contact
|
||||||
|
return nativetouch::inject::inject_synthetic_touch({ 0, 0 }, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// only a single synthetic contact is supported, so just use the first touch point
|
||||||
|
const auto &touch = touch_points.front();
|
||||||
|
POINT position { touch.x, touch.y };
|
||||||
|
if (canvas_w > 0 && canvas_h > 0) {
|
||||||
|
return nativetouch::inject::inject_synthetic_touch_from_canvas(
|
||||||
|
position,
|
||||||
|
{ canvas_w, canvas_h },
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
return nativetouch::inject::inject_synthetic_touch(position, true);
|
||||||
|
}
|
||||||
|
|
||||||
Touch::Touch() : Module("touch") {
|
Touch::Touch() : Module("touch") {
|
||||||
is_sdvx = avs::game::is_model("KFC");
|
is_sdvx = avs::game::is_model("KFC");
|
||||||
|
|
||||||
@@ -26,6 +49,25 @@ namespace api::modules {
|
|||||||
is_tdj_fhd = false;
|
is_tdj_fhd = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use_native = nativetouch::is_hooked();
|
||||||
|
|
||||||
|
// resolution the API/companion sends native touch coordinates in (after errata)
|
||||||
|
native_canvas_w = 0;
|
||||||
|
native_canvas_h = 0;
|
||||||
|
if (is_sdvx) {
|
||||||
|
// exceed gear subscreen, portrait after the rotation applied in apply_touch_errata
|
||||||
|
native_canvas_w = 1080;
|
||||||
|
native_canvas_h = 1920;
|
||||||
|
} else if (avs::game::is_model("LDJ")) {
|
||||||
|
// TDJ subscreen; FHD models are upscaled to 1080p by apply_touch_errata
|
||||||
|
native_canvas_w = is_tdj_fhd ? 1920 : 1280;
|
||||||
|
native_canvas_h = is_tdj_fhd ? 1080 : 720;
|
||||||
|
} else if (avs::game::is_model("M39")) {
|
||||||
|
// pop'n music API touch surface
|
||||||
|
native_canvas_w = 1280;
|
||||||
|
native_canvas_h = 800;
|
||||||
|
}
|
||||||
|
|
||||||
functions["read"] = std::bind(&Touch::read, this, _1, _2);
|
functions["read"] = std::bind(&Touch::read, this, _1, _2);
|
||||||
functions["write"] = std::bind(&Touch::write, this, _1, _2);
|
functions["write"] = std::bind(&Touch::write, this, _1, _2);
|
||||||
functions["write_reset"] = std::bind(&Touch::write_reset, this, _1, _2);
|
functions["write_reset"] = std::bind(&Touch::write_reset, this, _1, _2);
|
||||||
@@ -99,7 +141,11 @@ namespace api::modules {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// apply touch points
|
// apply touch points
|
||||||
touch_write_points(&touch_points);
|
if (use_native) {
|
||||||
|
write_native(touch_points);
|
||||||
|
} else {
|
||||||
|
touch_write_points(&touch_points);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,7 +169,43 @@ namespace api::modules {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remove all IDs
|
// remove all IDs
|
||||||
touch_remove_points(&touch_point_ids);
|
if (use_native) {
|
||||||
|
write_reset_native(touch_point_ids);
|
||||||
|
} else {
|
||||||
|
touch_remove_points(&touch_point_ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Touch::write_native(const std::vector<TouchPoint> &touch_points) {
|
||||||
|
if (touch_points.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(native_touch_mutex);
|
||||||
|
const auto touch_id = touch_points.front().id;
|
||||||
|
if (native_touch_id && *native_touch_id != touch_id) {
|
||||||
|
if (!inject_native_touch({}, native_canvas_w, native_canvas_h)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
native_touch_id.reset();
|
||||||
|
}
|
||||||
|
if (inject_native_touch(touch_points, native_canvas_w, native_canvas_h)) {
|
||||||
|
native_touch_id = touch_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Touch::write_reset_native(const std::vector<DWORD> &touch_point_ids) {
|
||||||
|
std::lock_guard<std::mutex> lock(native_touch_mutex);
|
||||||
|
if (!native_touch_id ||
|
||||||
|
std::find(
|
||||||
|
touch_point_ids.begin(),
|
||||||
|
touch_point_ids.end(),
|
||||||
|
*native_touch_id) == touch_point_ids.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (inject_native_touch({}, native_canvas_w, native_canvas_h)) {
|
||||||
|
native_touch_id.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Touch::apply_touch_errata(int &x, int &y) {
|
void Touch::apply_touch_errata(int &x, int &y) {
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "api/module.h"
|
#include "api/module.h"
|
||||||
#include "api/request.h"
|
#include "api/request.h"
|
||||||
|
#include "touch/touch.h"
|
||||||
|
|
||||||
namespace api::modules {
|
namespace api::modules {
|
||||||
|
|
||||||
@@ -13,11 +17,21 @@ namespace api::modules {
|
|||||||
private:
|
private:
|
||||||
bool is_sdvx;
|
bool is_sdvx;
|
||||||
bool is_tdj_fhd;
|
bool is_tdj_fhd;
|
||||||
|
bool use_native;
|
||||||
|
std::mutex native_touch_mutex;
|
||||||
|
std::optional<uint32_t> native_touch_id;
|
||||||
|
|
||||||
|
// resolution the API/companion sends native touch coordinates in (after errata);
|
||||||
|
// used to normalize before mapping into the native injection window
|
||||||
|
int native_canvas_w;
|
||||||
|
int native_canvas_h;
|
||||||
|
|
||||||
// function definitions
|
// function definitions
|
||||||
void read(Request &req, Response &res);
|
void read(Request &req, Response &res);
|
||||||
void write(Request &req, Response &res);
|
void write(Request &req, Response &res);
|
||||||
void write_reset(Request &req, Response &res);
|
void write_reset(Request &req, Response &res);
|
||||||
|
void write_native(const std::vector<TouchPoint> &touch_points);
|
||||||
|
void write_reset_native(const std::vector<DWORD> &touch_point_ids);
|
||||||
void apply_touch_errata(int &x, int &y);
|
void apply_touch_errata(int &x, int &y);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "serial.h"
|
#include "serial.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/precise_timer.h"
|
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -17,7 +18,6 @@ namespace api {
|
|||||||
controller->init_state(this->state);
|
controller->init_state(this->state);
|
||||||
this->thread = new std::thread([this] () {
|
this->thread = new std::thread([this] () {
|
||||||
log_warning("api::serial", "listening on {} (baud: {})", this->port, this->baud);
|
log_warning("api::serial", "listening on {} (baud: {})", this->port, this->baud);
|
||||||
timeutils::PreciseSleepTimer timer;
|
|
||||||
|
|
||||||
// read buffer
|
// read buffer
|
||||||
uint8_t read_buffer[16*1024];
|
uint8_t read_buffer[16*1024];
|
||||||
@@ -162,7 +162,7 @@ namespace api {
|
|||||||
|
|
||||||
// slow down on reconnect
|
// slow down on reconnect
|
||||||
if (this->running) {
|
if (this->running) {
|
||||||
timer.sleep(retry_time);
|
std::this_thread::sleep_for(std::chrono::milliseconds(retry_time));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1114,6 +1114,7 @@ namespace avs {
|
|||||||
#endif
|
#endif
|
||||||
// jubeat
|
// jubeat
|
||||||
{"jubeat.dll", 0x2000000},
|
{"jubeat.dll", 0x2000000},
|
||||||
|
{"jubeat2019.dll", 0x10000000},
|
||||||
|
|
||||||
// MUSECA
|
// MUSECA
|
||||||
{"museca.dll", 0xC000000},
|
{"museca.dll", 0xC000000},
|
||||||
@@ -1924,7 +1925,7 @@ namespace avs {
|
|||||||
#if !SPICE64
|
#if !SPICE64
|
||||||
// sdvx4 bad log config fix
|
// sdvx4 bad log config fix
|
||||||
if (avs::game::DLL_NAME == "soundvoltex.dll" && // it's too early for avs::game::is_model
|
if (avs::game::DLL_NAME == "soundvoltex.dll" && // it's too early for avs::game::is_model
|
||||||
property_search_safe(config, config_node, "/log/enable_console")) {
|
property_search(config, config_node, "/log/enable_console")) {
|
||||||
log_info("avs-core", "applying SDVX4 avs-config.xml fix for <log><enable_console>");
|
log_info("avs-core", "applying SDVX4 avs-config.xml fix for <log><enable_console>");
|
||||||
property_search_remove_safe(config, config_node, "/log/enable_console");
|
property_search_remove_safe(config, config_node, "/log/enable_console");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -591,6 +591,7 @@ namespace avs {
|
|||||||
// for proper reporting of Omnimix and other song packs
|
// for proper reporting of Omnimix and other song packs
|
||||||
if (_stricmp(EA3_MODEL, "LDJ") == 0 ||
|
if (_stricmp(EA3_MODEL, "LDJ") == 0 ||
|
||||||
_stricmp(EA3_MODEL, "L44") == 0 ||
|
_stricmp(EA3_MODEL, "L44") == 0 ||
|
||||||
|
_stricmp(EA3_MODEL, "T44") == 0 ||
|
||||||
_stricmp(EA3_MODEL, "M39") == 0 ||
|
_stricmp(EA3_MODEL, "M39") == 0 ||
|
||||||
_stricmp(EA3_MODEL, "KFC") == 0)
|
_stricmp(EA3_MODEL, "KFC") == 0)
|
||||||
{
|
{
|
||||||
|
|||||||
+66
-11
@@ -3,7 +3,9 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
#include "games/io.h"
|
||||||
#include "launcher/superexit.h"
|
#include "launcher/superexit.h"
|
||||||
|
#include "misc/eamuse.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
#include "rawinput/piuio.h"
|
#include "rawinput/piuio.h"
|
||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
@@ -62,7 +64,43 @@ std::vector<Button> GameAPI::Buttons::sortButtons(
|
|||||||
return sorted;
|
return sorted;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *manager, Button &_button, bool check_alts) {
|
namespace GameAPI::Buttons {
|
||||||
|
static State get_button_state(
|
||||||
|
rawinput::RawInputManager *manager,
|
||||||
|
Button &button,
|
||||||
|
bool check_alts,
|
||||||
|
bool check_modifiers);
|
||||||
|
|
||||||
|
static bool modifiers_pressed(rawinput::RawInputManager *manager, Button &button);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GameAPI::Buttons::modifiers_pressed(rawinput::RawInputManager *manager, Button &button) {
|
||||||
|
const auto modifier_mask = button.getModifierMask();
|
||||||
|
if (modifier_mask == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *modifier_buttons = games::get_buttons_modifiers(eamuse_get_game());
|
||||||
|
if (!modifier_buttons) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t index = 0; index < games::ModifierButtons::Size; index++) {
|
||||||
|
if ((modifier_mask & (UINT8_C(1) << index)) != 0 &&
|
||||||
|
(index >= modifier_buttons->size() ||
|
||||||
|
get_button_state(manager, modifier_buttons->at(index), true, false) !=
|
||||||
|
GameAPI::Buttons::BUTTON_PRESSED)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameAPI::Buttons::State GameAPI::Buttons::get_button_state(
|
||||||
|
rawinput::RawInputManager *manager,
|
||||||
|
Button &_button,
|
||||||
|
bool check_alts,
|
||||||
|
bool check_modifiers) {
|
||||||
|
|
||||||
// check override
|
// check override
|
||||||
if (_button.override_enabled) {
|
if (_button.override_enabled) {
|
||||||
@@ -76,6 +114,23 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
|||||||
std::optional<bool> window_has_focus;
|
std::optional<bool> window_has_focus;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
||||||
|
// skip bindings whose required modifiers are not held
|
||||||
|
//
|
||||||
|
// note that modifiers cannot process MIDI as the logic is written
|
||||||
|
// below, since MIDI buttons are event-based (on event, off event, etc)
|
||||||
|
// and cannot be correctly handled by a simple early return
|
||||||
|
// there is no explicit check for MIDI here, but the UI should have
|
||||||
|
// prevented it
|
||||||
|
if (check_modifiers && !modifiers_pressed(manager, *current_button)) {
|
||||||
|
button_count++;
|
||||||
|
if (!alternatives || alternatives->empty() ||
|
||||||
|
button_count - 1 >= alternatives->size()) {
|
||||||
|
return BUTTON_NOT_PRESSED;
|
||||||
|
}
|
||||||
|
current_button = &alternatives->at(button_count - 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// naive behavior
|
// naive behavior
|
||||||
if (current_button->isNaive()) {
|
if (current_button->isNaive()) {
|
||||||
GameAPI::Buttons::State state;
|
GameAPI::Buttons::State state;
|
||||||
@@ -469,6 +524,10 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Buttons::State Buttons::getState(rawinput::RawInputManager *manager, Button &button, bool check_alts) {
|
||||||
|
return get_button_state(manager, button, check_alts, true);
|
||||||
|
}
|
||||||
|
|
||||||
Buttons::State Buttons::getState(std::unique_ptr<rawinput::RawInputManager> &manager, Button &button, bool check_alts) {
|
Buttons::State Buttons::getState(std::unique_ptr<rawinput::RawInputManager> &manager, Button &button, bool check_alts) {
|
||||||
if (manager) {
|
if (manager) {
|
||||||
return getState(manager.get(), button, check_alts);
|
return getState(manager.get(), button, check_alts);
|
||||||
@@ -484,17 +543,13 @@ static float getVelocityHelper(rawinput::RawInputManager *manager, Button &butto
|
|||||||
return button.override_velocity;
|
return button.override_velocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
// naive behavior
|
|
||||||
if (button.isNaive()) {
|
|
||||||
if (button.getInvert()) {
|
|
||||||
return (GetAsyncKeyState(button.getVKey()) & 0x8000) ? 0.f : 1.f;
|
|
||||||
} else {
|
|
||||||
return (GetAsyncKeyState(button.getVKey()) & 0x8000) ? 1.f : 0.f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get button state
|
// get button state
|
||||||
Buttons::State button_state = Buttons::getState(manager, button, false);
|
const auto button_state = Buttons::getState(manager, button, false);
|
||||||
|
|
||||||
|
// naive bindings report their digital state as full or zero velocity
|
||||||
|
if (button.isNaive()) {
|
||||||
|
return button_state == Buttons::BUTTON_PRESSED ? 1.f : 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
// check if button isn't being pressed
|
// check if button isn't being pressed
|
||||||
if (button_state != Buttons::BUTTON_PRESSED) {
|
if (button_state != Buttons::BUTTON_PRESSED) {
|
||||||
|
|||||||
@@ -299,7 +299,27 @@ std::string Button::getMidiNoteString() {
|
|||||||
return fmt::format("{}{}", note_names[index % 12], ((index / 12) - 1));
|
return fmt::format("{}{}", note_names[index % 12], ((index / 12) - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
|
std::string Button::getDisplayString(rawinput::RawInputManager *manager) {
|
||||||
|
auto binding_display = this->getBindingDisplayString(manager);
|
||||||
|
auto modifiers = this->getModifierMask();
|
||||||
|
if (binding_display.empty() || modifiers == 0) {
|
||||||
|
return binding_display;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string modifier_display;
|
||||||
|
for (unsigned int index = 1; modifiers != 0; index++, modifiers >>= 1) {
|
||||||
|
if ((modifiers & UINT8_C(1)) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!modifier_display.empty()) {
|
||||||
|
modifier_display += "+";
|
||||||
|
}
|
||||||
|
modifier_display += fmt::format("Mod{}", index);
|
||||||
|
}
|
||||||
|
return modifier_display + "+" + binding_display;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Button::getBindingDisplayString(rawinput::RawInputManager *manager) {
|
||||||
|
|
||||||
// get VKey string
|
// get VKey string
|
||||||
auto vKey = (uint16_t) this->getVKey();
|
auto vKey = (uint16_t) this->getVKey();
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ private:
|
|||||||
std::string name;
|
std::string name;
|
||||||
std::string device_identifier = "";
|
std::string device_identifier = "";
|
||||||
unsigned short vKey = INVALID_VKEY;
|
unsigned short vKey = INVALID_VKEY;
|
||||||
|
uint8_t modifier_mask = 0;
|
||||||
|
|
||||||
// default bindings are always naive
|
// default bindings are always naive
|
||||||
unsigned short vKey_default = INVALID_VKEY;
|
unsigned short vKey_default = INVALID_VKEY;
|
||||||
@@ -65,6 +66,7 @@ private:
|
|||||||
unsigned short velocity_threshold = 0;
|
unsigned short velocity_threshold = 0;
|
||||||
|
|
||||||
std::string getMidiNoteString();
|
std::string getMidiNoteString();
|
||||||
|
std::string getBindingDisplayString(rawinput::RawInputManager *manager);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::string getVKeyString();
|
std::string getVKeyString();
|
||||||
@@ -111,6 +113,7 @@ public:
|
|||||||
device_identifier = "";
|
device_identifier = "";
|
||||||
analog_type = BAT_NONE;
|
analog_type = BAT_NONE;
|
||||||
bat_threshold = 0;
|
bat_threshold = 0;
|
||||||
|
modifier_mask = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getDisplayString(rawinput::RawInputManager* manager);
|
std::string getDisplayString(rawinput::RawInputManager* manager);
|
||||||
@@ -123,6 +126,14 @@ public:
|
|||||||
return this->name;
|
return this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline uint8_t getModifierMask() const {
|
||||||
|
return this->modifier_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void setModifierMask(uint8_t new_modifier_mask) {
|
||||||
|
this->modifier_mask = new_modifier_mask & UINT8_C(0x0F);
|
||||||
|
}
|
||||||
|
|
||||||
inline const std::string &getDeviceIdentifier() const {
|
inline const std::string &getDeviceIdentifier() const {
|
||||||
return this->device_identifier;
|
return this->device_identifier;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ bool Config::addGame(Game &game, bool save) {
|
|||||||
int bat_threshold = 0;
|
int bat_threshold = 0;
|
||||||
int velocity_threshold = 0;
|
int velocity_threshold = 0;
|
||||||
bool invert = false;
|
bool invert = false;
|
||||||
|
unsigned int modifier_mask = 0;
|
||||||
tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
||||||
const char *devid = gameButtonNode->Attribute("devid");
|
const char *devid = gameButtonNode->Attribute("devid");
|
||||||
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
||||||
@@ -173,6 +174,7 @@ bool Config::addGame(Game &game, bool save) {
|
|||||||
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
||||||
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
||||||
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
||||||
|
gameButtonNode->QueryUnsignedAttribute("modifiers", &modifier_mask);
|
||||||
if (attrError != tinyxml2::XMLError::XML_SUCCESS) {
|
if (attrError != tinyxml2::XMLError::XML_SUCCESS) {
|
||||||
gameButtonsNode->DeleteChild(gameButtonNode);
|
gameButtonsNode->DeleteChild(gameButtonNode);
|
||||||
gameButtonNode = this->configFile.NewElement("button");
|
gameButtonNode = this->configFile.NewElement("button");
|
||||||
@@ -185,6 +187,7 @@ bool Config::addGame(Game &game, bool save) {
|
|||||||
gameButtonNode->SetAttribute("bat_threshold", bat_threshold);
|
gameButtonNode->SetAttribute("bat_threshold", bat_threshold);
|
||||||
gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold);
|
gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold);
|
||||||
gameButtonNode->SetAttribute("invert", invert);
|
gameButtonNode->SetAttribute("invert", invert);
|
||||||
|
gameButtonNode->SetAttribute("modifiers", button->getModifierMask());
|
||||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||||
} else {
|
} else {
|
||||||
button->setVKey(static_cast<unsigned short int>(vKey));
|
button->setVKey(static_cast<unsigned short int>(vKey));
|
||||||
@@ -194,6 +197,7 @@ bool Config::addGame(Game &game, bool save) {
|
|||||||
button->setBatThreshold(bat_threshold);
|
button->setBatThreshold(bat_threshold);
|
||||||
button->setVelocityThreshold(velocity_threshold);
|
button->setVelocityThreshold(velocity_threshold);
|
||||||
button->setInvert(invert);
|
button->setInvert(invert);
|
||||||
|
button->setModifierMask(static_cast<uint8_t>(modifier_mask));
|
||||||
if (devid) {
|
if (devid) {
|
||||||
button->setDeviceIdentifier(devid);
|
button->setDeviceIdentifier(devid);
|
||||||
}
|
}
|
||||||
@@ -214,6 +218,7 @@ bool Config::addGame(Game &game, bool save) {
|
|||||||
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
||||||
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
||||||
gameButtonNode->SetAttribute("invert", it.getInvert());
|
gameButtonNode->SetAttribute("invert", it.getInvert());
|
||||||
|
gameButtonNode->SetAttribute("modifiers", it.getModifierMask());
|
||||||
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||||
}
|
}
|
||||||
@@ -440,6 +445,7 @@ bool Config::addGame(Game &game, bool save) {
|
|||||||
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
||||||
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
||||||
gameButtonNode->SetAttribute("invert", it.getInvert());
|
gameButtonNode->SetAttribute("invert", it.getInvert());
|
||||||
|
gameButtonNode->SetAttribute("modifiers", it.getModifierMask());
|
||||||
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||||
}
|
}
|
||||||
@@ -552,6 +558,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
|||||||
gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold());
|
gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold());
|
||||||
gameButtonNode->SetAttribute("invert", button.getInvert());
|
gameButtonNode->SetAttribute("invert", button.getInvert());
|
||||||
gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str());
|
gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str());
|
||||||
|
gameButtonNode->SetAttribute("modifiers", button.getModifierMask());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -569,6 +576,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
|||||||
gameButtonNode->SetAttribute("velocity_threshold", 0);
|
gameButtonNode->SetAttribute("velocity_threshold", 0);
|
||||||
gameButtonNode->SetAttribute("invert", false);
|
gameButtonNode->SetAttribute("invert", false);
|
||||||
gameButtonNode->SetAttribute("devid", "");
|
gameButtonNode->SetAttribute("devid", "");
|
||||||
|
gameButtonNode->SetAttribute("modifiers", 0);
|
||||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -964,6 +972,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
|||||||
int bat_threshold = 0;
|
int bat_threshold = 0;
|
||||||
int velocity_threshold = 0;
|
int velocity_threshold = 0;
|
||||||
bool invert = false;
|
bool invert = false;
|
||||||
|
unsigned int modifier_mask = 0;
|
||||||
gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
||||||
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
||||||
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
|
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
|
||||||
@@ -971,6 +980,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
|||||||
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
||||||
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
||||||
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
||||||
|
gameButtonNode->QueryUnsignedAttribute("modifiers", &modifier_mask);
|
||||||
const char *devid = gameButtonNode->Attribute("devid");
|
const char *devid = gameButtonNode->Attribute("devid");
|
||||||
|
|
||||||
// find alternative
|
// find alternative
|
||||||
@@ -986,6 +996,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
|||||||
alt.setBatThreshold(bat_threshold);
|
alt.setBatThreshold(bat_threshold);
|
||||||
alt.setVelocityThreshold(velocity_threshold);
|
alt.setVelocityThreshold(velocity_threshold);
|
||||||
alt.setInvert(invert);
|
alt.setInvert(invert);
|
||||||
|
alt.setModifierMask(static_cast<uint8_t>(modifier_mask));
|
||||||
if (devid) {
|
if (devid) {
|
||||||
alt.setDeviceIdentifier(std::string(devid));
|
alt.setDeviceIdentifier(std::string(devid));
|
||||||
}
|
}
|
||||||
@@ -1005,6 +1016,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
|||||||
button.setBatThreshold(bat_threshold);
|
button.setBatThreshold(bat_threshold);
|
||||||
button.setVelocityThreshold(velocity_threshold);
|
button.setVelocityThreshold(velocity_threshold);
|
||||||
button.setInvert(invert);
|
button.setInvert(invert);
|
||||||
|
button.setModifierMask(static_cast<uint8_t>(modifier_mask));
|
||||||
if (devid) {
|
if (devid) {
|
||||||
button.setDeviceIdentifier(devid);
|
button.setDeviceIdentifier(devid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ namespace overlay::windows {
|
|||||||
el->SetAttribute("debounce_down", entry.debounce_down);
|
el->SetAttribute("debounce_down", entry.debounce_down);
|
||||||
el->SetAttribute("bat_threshold", entry.bat_threshold);
|
el->SetAttribute("bat_threshold", entry.bat_threshold);
|
||||||
el->SetAttribute("velocity_threshold", entry.velocity_threshold);
|
el->SetAttribute("velocity_threshold", entry.velocity_threshold);
|
||||||
|
el->SetAttribute("modifiers", entry.modifier_mask);
|
||||||
parent->InsertEndChild(el);
|
parent->InsertEndChild(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +55,10 @@ namespace overlay::windows {
|
|||||||
el->QueryIntAttribute("bat_threshold", &bat);
|
el->QueryIntAttribute("bat_threshold", &bat);
|
||||||
entry.bat_threshold = bat;
|
entry.bat_threshold = bat;
|
||||||
|
|
||||||
|
unsigned int modifier_mask = 0;
|
||||||
|
el->QueryUnsignedAttribute("modifiers", &modifier_mask);
|
||||||
|
entry.modifier_mask = static_cast<uint8_t>(modifier_mask & UINT8_C(0x0F));
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +165,28 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// modifier buttons
|
||||||
|
auto *mod_el = tmpl_el->FirstChildElement("modifier_buttons");
|
||||||
|
if (mod_el) {
|
||||||
|
auto *btn_el = mod_el->FirstChildElement("button");
|
||||||
|
while (btn_el) {
|
||||||
|
const char *btn_name = btn_el->Attribute("name");
|
||||||
|
std::string btn_name_str = btn_name ? btn_name : "";
|
||||||
|
|
||||||
|
TemplateButtonBinding *binding = nullptr;
|
||||||
|
for (auto &b : tmpl.modifier_buttons) {
|
||||||
|
if (b.name == btn_name_str) { binding = &b; break; }
|
||||||
|
}
|
||||||
|
if (!binding) {
|
||||||
|
tmpl.modifier_buttons.push_back(
|
||||||
|
{btn_name_str, read_button_entry(btn_el), {}});
|
||||||
|
} else {
|
||||||
|
binding->alternatives.push_back(read_button_entry(btn_el));
|
||||||
|
}
|
||||||
|
btn_el = btn_el->NextSiblingElement("button");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// keypad buttons
|
// keypad buttons
|
||||||
auto *kp_el = tmpl_el->FirstChildElement("keypad_buttons");
|
auto *kp_el = tmpl_el->FirstChildElement("keypad_buttons");
|
||||||
if (kp_el) {
|
if (kp_el) {
|
||||||
@@ -244,6 +271,7 @@ namespace overlay::windows {
|
|||||||
bool save_user_template(
|
bool save_user_template(
|
||||||
const ControllerTemplate &tmpl,
|
const ControllerTemplate &tmpl,
|
||||||
const bool save_buttons,
|
const bool save_buttons,
|
||||||
|
const bool save_modifiers,
|
||||||
const bool save_keypads,
|
const bool save_keypads,
|
||||||
const bool save_analogs,
|
const bool save_analogs,
|
||||||
const bool save_lights) {
|
const bool save_lights) {
|
||||||
@@ -300,6 +328,24 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
tmpl_el->InsertEndChild(buttons_el);
|
tmpl_el->InsertEndChild(buttons_el);
|
||||||
|
|
||||||
|
// modifier buttons - skip unbound entries
|
||||||
|
if (!tmpl.modifier_buttons.empty()) {
|
||||||
|
auto *mod_el = doc.NewElement("modifier_buttons");
|
||||||
|
if (save_modifiers) {
|
||||||
|
for (auto &btn : tmpl.modifier_buttons) {
|
||||||
|
if (!btn.primary.is_unbound()) {
|
||||||
|
write_button_entry(doc, mod_el, btn.name, btn.primary);
|
||||||
|
}
|
||||||
|
for (auto &alt : btn.alternatives) {
|
||||||
|
if (!alt.is_unbound()) {
|
||||||
|
write_button_entry(doc, mod_el, btn.name, alt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmpl_el->InsertEndChild(mod_el);
|
||||||
|
}
|
||||||
|
|
||||||
// keypad buttons — skip unbound entries
|
// keypad buttons — skip unbound entries
|
||||||
if (!tmpl.keypad_buttons.empty()) {
|
if (!tmpl.keypad_buttons.empty()) {
|
||||||
auto *kp_el = doc.NewElement("keypad_buttons");
|
auto *kp_el = doc.NewElement("keypad_buttons");
|
||||||
|
|||||||
+9
-1
@@ -878,7 +878,15 @@ inline void ImGui::FileBrowser::UpdateFileRecords()
|
|||||||
|
|
||||||
inline void ImGui::FileBrowser::SetPwdUncatched(const std::filesystem::path &pwd)
|
inline void ImGui::FileBrowser::SetPwdUncatched(const std::filesystem::path &pwd)
|
||||||
{
|
{
|
||||||
pwd_ = absolute(pwd);
|
// spice2x
|
||||||
|
// avoid MinGW std::filesystem::absolute() duplicating UNC server/share
|
||||||
|
// prefixes, which makes directory_iterator() throw during startup
|
||||||
|
// spice2x
|
||||||
|
const auto &nativePwd = pwd.native();
|
||||||
|
const bool hasUncPrefix = nativePwd.size() >= 2 &&
|
||||||
|
(nativePwd[0] == '\\' || nativePwd[0] == '/') &&
|
||||||
|
(nativePwd[1] == '\\' || nativePwd[1] == '/');
|
||||||
|
pwd_ = hasUncPrefix ? pwd : absolute(pwd);
|
||||||
UpdateFileRecords();
|
UpdateFileRecords();
|
||||||
selectedFilenames_.clear();
|
selectedFilenames_.clear();
|
||||||
(*inputNameBuf_)[0] = '\0';
|
(*inputNameBuf_)[0] = '\0';
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "acioemu/handle.h"
|
#include "acioemu/handle.h"
|
||||||
#include "misc/wintouchemu.h"
|
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
#include "bi2x_hook.h"
|
#include "bi2x_hook.h"
|
||||||
#include "trackball.h"
|
#include "trackball.h"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
#include "misc/wintouchemu.h"
|
#include "misc/wintouchemu.h"
|
||||||
#include "overlay/overlay.h"
|
#include "overlay/overlay.h"
|
||||||
|
#include "touch/native/nativetouchhook.h"
|
||||||
#include "util/cpuutils.h"
|
#include "util/cpuutils.h"
|
||||||
#include "util/deferlog.h"
|
#include "util/deferlog.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
@@ -653,9 +654,13 @@ namespace games::gitadora {
|
|||||||
// monitor/touch hooks (windowed or full screen)
|
// monitor/touch hooks (windowed or full screen)
|
||||||
if (GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
if (GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||||
// enable touch hook for subscreen overlay
|
// enable touch hook for subscreen overlay
|
||||||
wintouchemu::FORCE = true;
|
const auto native_touch_ready = !wintouchemu::FORCE &&
|
||||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
nativetouch::hook(avs::game::DLL_INSTANCE);
|
||||||
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
|
if (!native_touch_ready) {
|
||||||
|
wintouchemu::FORCE = true;
|
||||||
|
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||||
|
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
#if !SPICE_XP
|
#if !SPICE_XP
|
||||||
|
|
||||||
|
|||||||
@@ -534,7 +534,7 @@ namespace games::iidx {
|
|||||||
if (mediaTypePointer && mediaTypePointer->IsString()) {
|
if (mediaTypePointer && mediaTypePointer->IsString()) {
|
||||||
std::string mediaType = mediaTypePointer->GetString();
|
std::string mediaType = mediaTypePointer->GetString();
|
||||||
if (mediaType.length() > 0) {
|
if (mediaType.length() > 0) {
|
||||||
camera->m_selectedMediaTypeDescription = mediaType;
|
camera->SetSelectedMediaTypeDescription(mediaType);
|
||||||
camera->m_useAutoMediaType = false;
|
camera->m_useAutoMediaType = false;
|
||||||
} else {
|
} else {
|
||||||
camera->m_useAutoMediaType = true;
|
camera->m_useAutoMediaType = true;
|
||||||
@@ -547,7 +547,7 @@ namespace games::iidx {
|
|||||||
std::string drawModeString = drawModePointer->GetString();
|
std::string drawModeString = drawModePointer->GetString();
|
||||||
for (int j = 0; j < DRAW_MODE_SIZE; j++) {
|
for (int j = 0; j < DRAW_MODE_SIZE; j++) {
|
||||||
if (DRAW_MODE_LABELS[j].compare(drawModeString) == 0) {
|
if (DRAW_MODE_LABELS[j].compare(drawModeString) == 0) {
|
||||||
camera->m_drawMode = (LocalCameraDrawMode) j;
|
camera->m_drawMode.store((LocalCameraDrawMode) j);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -556,12 +556,12 @@ namespace games::iidx {
|
|||||||
// Flip
|
// Flip
|
||||||
auto flipHorizontalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipHorizontal").Get(doc);
|
auto flipHorizontalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipHorizontal").Get(doc);
|
||||||
if (flipHorizontalPointer && flipHorizontalPointer->IsBool()) {
|
if (flipHorizontalPointer && flipHorizontalPointer->IsBool()) {
|
||||||
camera->m_flipHorizontal = flipHorizontalPointer->GetBool();
|
camera->m_flipHorizontal.store(flipHorizontalPointer->GetBool());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto flipVerticalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipVertical").Get(doc);
|
auto flipVerticalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipVertical").Get(doc);
|
||||||
if (flipVerticalPointer && flipVerticalPointer->IsBool()) {
|
if (flipVerticalPointer && flipVerticalPointer->IsBool()) {
|
||||||
camera->m_flipVertical = flipVerticalPointer->GetBool();
|
camera->m_flipVertical.store(flipVerticalPointer->GetBool());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow manual control
|
// Allow manual control
|
||||||
@@ -625,15 +625,17 @@ namespace games::iidx {
|
|||||||
if (camera->m_useAutoMediaType) {
|
if (camera->m_useAutoMediaType) {
|
||||||
rapidjson::Pointer(root + "MediaType").Set(doc, "");
|
rapidjson::Pointer(root + "MediaType").Set(doc, "");
|
||||||
} else {
|
} else {
|
||||||
rapidjson::Pointer(root + "MediaType").Set(doc, camera->m_selectedMediaTypeDescription);
|
const auto mediaTypeDescription = camera->GetSelectedMediaTypeDescription();
|
||||||
|
rapidjson::Pointer(root + "MediaType").Set(doc, mediaTypeDescription);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw Mode
|
// Draw Mode
|
||||||
rapidjson::Pointer(root + "DrawMode").Set(doc, DRAW_MODE_LABELS[camera->m_drawMode]);
|
const auto drawMode = camera->m_drawMode.load();
|
||||||
|
rapidjson::Pointer(root + "DrawMode").Set(doc, DRAW_MODE_LABELS[drawMode]);
|
||||||
|
|
||||||
// Flip
|
// Flip
|
||||||
rapidjson::Pointer(root + "FlipHorizontal").Set(doc, camera->m_flipHorizontal);
|
rapidjson::Pointer(root + "FlipHorizontal").Set(doc, camera->m_flipHorizontal.load());
|
||||||
rapidjson::Pointer(root + "FlipVertical").Set(doc, camera->m_flipVertical);
|
rapidjson::Pointer(root + "FlipVertical").Set(doc, camera->m_flipVertical.load());
|
||||||
|
|
||||||
// Manual control
|
// Manual control
|
||||||
rapidjson::Pointer(root + "AllowManualControl").Set(doc, camera->m_allowManualControl);
|
rapidjson::Pointer(root + "AllowManualControl").Set(doc, camera->m_allowManualControl);
|
||||||
|
|||||||
@@ -20,9 +20,9 @@
|
|||||||
#include "hooks/sleephook.h"
|
#include "hooks/sleephook.h"
|
||||||
#include "launcher/options.h"
|
#include "launcher/options.h"
|
||||||
#include "touch/touch.h"
|
#include "touch/touch.h"
|
||||||
#include "misc/nativetouchhook.h"
|
#include "touch/native/nativetouchhook.h"
|
||||||
#include "misc/wintouchemu.h"
|
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
|
#include "misc/wintouchemu.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/deferlog.h"
|
#include "util/deferlog.h"
|
||||||
#include "util/fileutils.h"
|
#include "util/fileutils.h"
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
#include "bi2x_hook.h"
|
#include "bi2x_hook.h"
|
||||||
#include "ezusb.h"
|
#include "ezusb.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
|
#include "poke.h"
|
||||||
|
|
||||||
static decltype(RegCloseKey) *RegCloseKey_orig = nullptr;
|
static decltype(RegCloseKey) *RegCloseKey_orig = nullptr;
|
||||||
static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr;
|
static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr;
|
||||||
@@ -63,7 +64,8 @@ namespace games::iidx {
|
|||||||
bool TDJ_MODE = false;
|
bool TDJ_MODE = false;
|
||||||
bool FORCE_720P = false;
|
bool FORCE_720P = false;
|
||||||
bool DISABLE_ESPEC_IO = false;
|
bool DISABLE_ESPEC_IO = false;
|
||||||
bool NATIVE_TOUCH = false;
|
bool NATIVE_TOUCH = true;
|
||||||
|
bool ENABLE_POKE = false;
|
||||||
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
|
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
|
||||||
std::optional<std::string> SOUND_OUTPUT_DEVICE_IN_EFFECT = std::nullopt;
|
std::optional<std::string> SOUND_OUTPUT_DEVICE_IN_EFFECT = std::nullopt;
|
||||||
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||||||
@@ -350,12 +352,15 @@ namespace games::iidx {
|
|||||||
// need to hook `avs2-core.dll` so AVS win32fs operations go through rom hook
|
// need to hook `avs2-core.dll` so AVS win32fs operations go through rom hook
|
||||||
devicehook_init(avs::core::DLL_INSTANCE);
|
devicehook_init(avs::core::DLL_INSTANCE);
|
||||||
|
|
||||||
if (NATIVE_TOUCH) {
|
NATIVE_TOUCH = !wintouchemu::FORCE &&
|
||||||
nativetouchhook::hook(avs::game::DLL_INSTANCE);
|
nativetouch::hook(avs::game::DLL_INSTANCE);
|
||||||
} else {
|
if (!NATIVE_TOUCH) {
|
||||||
wintouchemu::FORCE = true;
|
wintouchemu::FORCE = true;
|
||||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||||
wintouchemu::hook_title_ends("beatmania IIDX", "main", avs::game::DLL_INSTANCE);
|
wintouchemu::hook_title_ends(
|
||||||
|
"beatmania IIDX",
|
||||||
|
"main",
|
||||||
|
avs::game::DLL_INSTANCE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,6 +538,12 @@ namespace games::iidx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IIDXGame::post_attach() {
|
||||||
|
if (ENABLE_POKE) {
|
||||||
|
poke::enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void IIDXGame::detach() {
|
void IIDXGame::detach() {
|
||||||
Game::detach();
|
Game::detach();
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ namespace games::iidx {
|
|||||||
extern bool FORCE_720P;
|
extern bool FORCE_720P;
|
||||||
extern bool DISABLE_ESPEC_IO;
|
extern bool DISABLE_ESPEC_IO;
|
||||||
extern bool NATIVE_TOUCH;
|
extern bool NATIVE_TOUCH;
|
||||||
|
extern bool ENABLE_POKE;
|
||||||
extern std::optional<std::string> SOUND_OUTPUT_DEVICE;
|
extern std::optional<std::string> SOUND_OUTPUT_DEVICE;
|
||||||
extern std::optional<std::string> ASIO_DRIVER;
|
extern std::optional<std::string> ASIO_DRIVER;
|
||||||
extern uint8_t DIGITAL_TT_SENS;
|
extern uint8_t DIGITAL_TT_SENS;
|
||||||
@@ -53,6 +54,7 @@ namespace games::iidx {
|
|||||||
|
|
||||||
virtual void attach() override;
|
virtual void attach() override;
|
||||||
virtual void pre_attach() override;
|
virtual void pre_attach() override;
|
||||||
|
virtual void post_attach() override;
|
||||||
virtual void detach() override;
|
virtual void detach() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#if SPICE64 && !SPICE_XP
|
#if SPICE64 && !SPICE_XP
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <d3d9.h>
|
#include <d3d9.h>
|
||||||
#include <dxva2api.h>
|
#include <dxva2api.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -66,6 +67,8 @@ extern std::string CAMERA_CONTROL_LABELS[];
|
|||||||
extern std::string DRAW_MODE_LABELS[];
|
extern std::string DRAW_MODE_LABELS[];
|
||||||
|
|
||||||
namespace games::iidx {
|
namespace games::iidx {
|
||||||
|
class IIDXCameraSourceReaderCallback;
|
||||||
|
|
||||||
namespace Camera {
|
namespace Camera {
|
||||||
struct PlayVideoCamera {
|
struct PlayVideoCamera {
|
||||||
IDirect3DTexture9** d3d9_texture(const uintptr_t offset) {
|
IDirect3DTexture9** d3d9_texture(const uintptr_t offset) {
|
||||||
@@ -87,7 +90,7 @@ namespace games::iidx {
|
|||||||
|
|
||||||
class IIDXLocalCamera {
|
class IIDXLocalCamera {
|
||||||
protected:
|
protected:
|
||||||
virtual ~IIDXLocalCamera() {};
|
virtual ~IIDXLocalCamera();
|
||||||
|
|
||||||
LONG m_nRefCount;
|
LONG m_nRefCount;
|
||||||
CRITICAL_SECTION m_critsec;
|
CRITICAL_SECTION m_critsec;
|
||||||
@@ -101,9 +104,14 @@ namespace games::iidx {
|
|||||||
// For reading frames from Camera
|
// For reading frames from Camera
|
||||||
IMFMediaSource *m_pSource = nullptr;
|
IMFMediaSource *m_pSource = nullptr;
|
||||||
IMFSourceReader *m_pSourceReader = nullptr;
|
IMFSourceReader *m_pSourceReader = nullptr;
|
||||||
|
IMFSourceReaderEx *m_pSourceReaderEx = nullptr;
|
||||||
|
IIDXCameraSourceReaderCallback *m_pSourceReaderCallback = nullptr;
|
||||||
|
std::mutex m_mediaTypeMutex;
|
||||||
|
IMFMediaType *m_pendingMediaType = nullptr;
|
||||||
|
int m_selectedMediaTypeIndex = 0;
|
||||||
|
std::string m_selectedMediaTypeDescription = "";
|
||||||
|
|
||||||
// Camera Format information
|
// Camera Format information
|
||||||
double m_frameRate = 0;
|
|
||||||
LONG m_cameraWidth;
|
LONG m_cameraWidth;
|
||||||
LONG m_cameraHeight;
|
LONG m_cameraHeight;
|
||||||
|
|
||||||
@@ -129,6 +137,12 @@ namespace games::iidx {
|
|||||||
LPDIRECT3DTEXTURE9 m_conversionTexture = nullptr;
|
LPDIRECT3DTEXTURE9 m_conversionTexture = nullptr;
|
||||||
IDirect3DSurface9 *m_pConversionSurf = nullptr;
|
IDirect3DSurface9 *m_pConversionSurf = nullptr;
|
||||||
|
|
||||||
|
// upload surface for decoded camera frames returned in system memory
|
||||||
|
IDirect3DSurface9 *m_pDecodedSurf = nullptr;
|
||||||
|
GUID m_decodedSubtype = GUID_NULL;
|
||||||
|
GUID m_outputSubtype = GUID_NULL;
|
||||||
|
bool m_drawErrorLogged = false;
|
||||||
|
|
||||||
// Texture for custom transform (e.g. horizontal flip)
|
// Texture for custom transform (e.g. horizontal flip)
|
||||||
LPDIRECT3DTEXTURE9 m_transformTexture = nullptr;
|
LPDIRECT3DTEXTURE9 m_transformTexture = nullptr;
|
||||||
IDirect3DSurface9 *m_pTransformSurf = nullptr;
|
IDirect3DSurface9 *m_pTransformSurf = nullptr;
|
||||||
@@ -153,21 +167,19 @@ namespace games::iidx {
|
|||||||
BOOL m_initialized = false;
|
BOOL m_initialized = false;
|
||||||
|
|
||||||
// True if all the setup steps succeeded
|
// True if all the setup steps succeeded
|
||||||
BOOL m_active = false;
|
std::atomic_bool m_active = false;
|
||||||
|
|
||||||
// Media type select
|
// Media type select
|
||||||
std::vector<MediaTypeInfo> m_mediaTypeInfos = {};
|
std::vector<MediaTypeInfo> m_mediaTypeInfos = {};
|
||||||
int m_selectedMediaTypeIndex = 0;
|
|
||||||
bool m_useAutoMediaType = true;
|
bool m_useAutoMediaType = true;
|
||||||
IMFMediaType *m_pAutoMediaType = nullptr;
|
IMFMediaType *m_pAutoMediaType = nullptr;
|
||||||
std::string m_selectedMediaTypeDescription = "";
|
|
||||||
bool m_allowManualControl = false;
|
bool m_allowManualControl = false;
|
||||||
|
|
||||||
LocalCameraDrawMode m_drawMode = DrawModeCrop4_3;
|
std::atomic<LocalCameraDrawMode> m_drawMode = DrawModeCrop4_3;
|
||||||
|
|
||||||
// Render processing
|
// Render processing
|
||||||
bool m_flipHorizontal = false;
|
std::atomic_bool m_flipHorizontal = false;
|
||||||
bool m_flipVertical = false;
|
std::atomic_bool m_flipVertical = false;
|
||||||
|
|
||||||
IIDXLocalCamera(
|
IIDXLocalCamera(
|
||||||
std::string name,
|
std::string name,
|
||||||
@@ -184,11 +196,14 @@ namespace games::iidx {
|
|||||||
HRESULT GetCameraControlProp(int index, CameraControlProp *pProp);
|
HRESULT GetCameraControlProp(int index, CameraControlProp *pProp);
|
||||||
HRESULT SetCameraControlProp(int index, long value, long flags);
|
HRESULT SetCameraControlProp(int index, long value, long flags);
|
||||||
HRESULT ResetCameraControlProps();
|
HRESULT ResetCameraControlProps();
|
||||||
HRESULT FlushDrawCommands();
|
|
||||||
std::string GetName();
|
std::string GetName();
|
||||||
std::string GetFriendlyName();
|
std::string GetFriendlyName();
|
||||||
std::string GetSymLink();
|
std::string GetSymLink();
|
||||||
|
int GetSelectedMediaTypeIndex();
|
||||||
|
std::string GetSelectedMediaTypeDescription();
|
||||||
|
void SetSelectedMediaTypeDescription(const std::string &description);
|
||||||
HRESULT ChangeMediaType(IMFMediaType *pType);
|
HRESULT ChangeMediaType(IMFMediaType *pType);
|
||||||
|
void RequestMediaType(IMFMediaType *pType);
|
||||||
HRESULT StartCapture();
|
HRESULT StartCapture();
|
||||||
void UpdateDrawRect();
|
void UpdateDrawRect();
|
||||||
|
|
||||||
@@ -197,12 +212,22 @@ namespace games::iidx {
|
|||||||
void CreateThread();
|
void CreateThread();
|
||||||
MediaTypeInfo GetMediaTypeInfo(IMFMediaType *pType);
|
MediaTypeInfo GetMediaTypeInfo(IMFMediaType *pType);
|
||||||
std::string GetVideoFormatName(GUID subtype);
|
std::string GetVideoFormatName(GUID subtype);
|
||||||
HRESULT TryMediaType(IMFMediaType *pType, UINT32 *pBestWidth, double *pBestFrameRate);
|
static bool CompareMediaTypes(const MediaTypeInfo &a, const MediaTypeInfo &b);
|
||||||
|
bool MatchesPreferredAspect(const MediaTypeInfo &info) const;
|
||||||
|
static bool IsBetterAutoType(const MediaTypeInfo &candidate, const MediaTypeInfo ¤t);
|
||||||
|
IMFMediaType *FindBestNativeAutoType(bool requirePreferredAspect) const;
|
||||||
|
IMFMediaType *FindBestAutoType(const GUID &subtype, bool requirePreferredAspect) const;
|
||||||
|
HRESULT ValidateMediaType(IMFMediaType *pType);
|
||||||
HRESULT InitTargetTexture();
|
HRESULT InitTargetTexture();
|
||||||
|
HRESULT EnsureTransformTextures();
|
||||||
HRESULT InitCameraControl();
|
HRESULT InitCameraControl();
|
||||||
|
void SetSelectedMediaType(int index, const std::string &description);
|
||||||
|
bool HasPendingMediaType();
|
||||||
|
HRESULT ApplyPendingMediaType();
|
||||||
|
HRESULT UploadDecodedSample(IMFMediaBuffer *pSrcBuffer);
|
||||||
HRESULT DrawSample(IMFMediaBuffer *pSrcBuffer);
|
HRESULT DrawSample(IMFMediaBuffer *pSrcBuffer);
|
||||||
HRESULT ReadSample();
|
HRESULT ReadSample();
|
||||||
LPDIRECT3DTEXTURE9 Render();
|
HRESULT Render();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ namespace games::iidx {
|
|||||||
_In_ UINT32 cInitialSize
|
_In_ UINT32 cInitialSize
|
||||||
);
|
);
|
||||||
|
|
||||||
|
typedef HRESULT (__stdcall * MFCreateMediaType_t)(
|
||||||
|
_Out_ IMFMediaType** ppMFType
|
||||||
|
);
|
||||||
|
|
||||||
typedef HRESULT (__stdcall * MFEnumDeviceSources_t)(
|
typedef HRESULT (__stdcall * MFEnumDeviceSources_t)(
|
||||||
_In_ IMFAttributes* pAttributes,
|
_In_ IMFAttributes* pAttributes,
|
||||||
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
||||||
@@ -35,6 +39,7 @@ namespace games::iidx {
|
|||||||
);
|
);
|
||||||
|
|
||||||
static MFCreateAttributes_t MFCreateAttributes = nullptr;
|
static MFCreateAttributes_t MFCreateAttributes = nullptr;
|
||||||
|
static MFCreateMediaType_t MFCreateMediaType = nullptr;
|
||||||
static MFEnumDeviceSources_t MFEnumDeviceSources = nullptr;
|
static MFEnumDeviceSources_t MFEnumDeviceSources = nullptr;
|
||||||
static MFCreateSourceReaderFromMediaSource_t MFCreateSourceReaderFromMediaSource = nullptr;
|
static MFCreateSourceReaderFromMediaSource_t MFCreateSourceReaderFromMediaSource = nullptr;
|
||||||
static MFGetService_t MFGetService = nullptr;
|
static MFGetService_t MFGetService = nullptr;
|
||||||
@@ -66,6 +71,12 @@ namespace games::iidx {
|
|||||||
log_fatal("mf_wrappers", "MFCreateAttributes failed to hook");
|
log_fatal("mf_wrappers", "MFCreateAttributes failed to hook");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MFCreateMediaType = (MFCreateMediaType_t)
|
||||||
|
libutils::get_proc(mfplat_dll, "MFCreateMediaType");
|
||||||
|
if (!MFCreateMediaType) {
|
||||||
|
log_fatal("mf_wrappers", "MFCreateMediaType failed to hook");
|
||||||
|
}
|
||||||
|
|
||||||
MFEnumDeviceSources = (MFEnumDeviceSources_t)
|
MFEnumDeviceSources = (MFEnumDeviceSources_t)
|
||||||
libutils::get_proc(mf_dll, "MFEnumDeviceSources");
|
libutils::get_proc(mf_dll, "MFEnumDeviceSources");
|
||||||
if (!MFEnumDeviceSources) {
|
if (!MFEnumDeviceSources) {
|
||||||
@@ -92,6 +103,11 @@ namespace games::iidx {
|
|||||||
return MFCreateAttributes(ppMFAttributes, cInitialSize);
|
return MFCreateAttributes(ppMFAttributes, cInitialSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HRESULT WrappedMFCreateMediaType (
|
||||||
|
_Out_ IMFMediaType** ppMFType) {
|
||||||
|
return MFCreateMediaType(ppMFType);
|
||||||
|
}
|
||||||
|
|
||||||
HRESULT WrappedMFEnumDeviceSources (
|
HRESULT WrappedMFEnumDeviceSources (
|
||||||
_In_ IMFAttributes* pAttributes,
|
_In_ IMFAttributes* pAttributes,
|
||||||
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ namespace games::iidx {
|
|||||||
_Out_ IMFAttributes** ppMFAttributes,
|
_Out_ IMFAttributes** ppMFAttributes,
|
||||||
_In_ UINT32 cInitialSize);
|
_In_ UINT32 cInitialSize);
|
||||||
|
|
||||||
|
HRESULT WrappedMFCreateMediaType (
|
||||||
|
_Out_ IMFMediaType** ppMFType);
|
||||||
|
|
||||||
HRESULT WrappedMFEnumDeviceSources (
|
HRESULT WrappedMFEnumDeviceSources (
|
||||||
_In_ IMFAttributes* pAttributes,
|
_In_ IMFAttributes* pAttributes,
|
||||||
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
||||||
|
|||||||
+26
-147
@@ -1,31 +1,17 @@
|
|||||||
#include "poke.h"
|
#include "poke.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "windows.h"
|
#include "windows.h"
|
||||||
#include "cfg/screen_resize.h"
|
|
||||||
#include "games/io.h"
|
#include "games/io.h"
|
||||||
#include "games/iidx/iidx.h"
|
#include "games/iidx/iidx.h"
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
#include "launcher/shutdown.h"
|
#include "launcher/shutdown.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "overlay/overlay.h"
|
#include "touch/native/inject.h"
|
||||||
#include "overlay/windows/generic_sub.h"
|
|
||||||
#include "rawinput/rawinput.h"
|
|
||||||
#include "touch/touch.h"
|
#include "touch/touch.h"
|
||||||
#include "util/libutils.h"
|
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/precise_timer.h"
|
|
||||||
|
|
||||||
#define POKE_NATIVE_TOUCH 0
|
|
||||||
|
|
||||||
#if POKE_NATIVE_TOUCH
|
|
||||||
static HINSTANCE USER32_INSTANCE = nullptr;
|
|
||||||
typedef BOOL (WINAPI *InitializeTouchInjection_t)(UINT32, DWORD);
|
|
||||||
typedef BOOL (WINAPI *InjectTouchInput_t)(UINT32, POINTER_TOUCH_INFO*);
|
|
||||||
static InitializeTouchInjection_t pInitializeTouchInjection = nullptr;
|
|
||||||
static InjectTouchInput_t pInjectTouchInput = nullptr;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace games::iidx::poke {
|
namespace games::iidx::poke {
|
||||||
|
|
||||||
@@ -116,80 +102,13 @@ namespace games::iidx::poke {
|
|||||||
{"1/D", 50},
|
{"1/D", 50},
|
||||||
};
|
};
|
||||||
|
|
||||||
#if POKE_NATIVE_TOUCH
|
static void inject_native_touch_points(const std::vector<TouchPoint> &touch_points, bool down) {
|
||||||
void initialize_native_touch_library() {
|
if (touch_points.empty()) {
|
||||||
if (USER32_INSTANCE == nullptr) {
|
|
||||||
USER32_INSTANCE = libutils::load_library("user32.dll");
|
|
||||||
}
|
|
||||||
|
|
||||||
pInitializeTouchInjection = libutils::try_proc<InitializeTouchInjection_t>(
|
|
||||||
USER32_INSTANCE, "InitializeTouchInjection");
|
|
||||||
pInjectTouchInput = libutils::try_proc<InjectTouchInput_t>(
|
|
||||||
USER32_INSTANCE, "InjectTouchInput");
|
|
||||||
}
|
|
||||||
|
|
||||||
void emulate_native_touch(TouchPoint tp, bool is_down) {
|
|
||||||
if (pInjectTouchInput == nullptr) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
POINTER_TOUCH_INFO contact;
|
const auto &touch = touch_points.front();
|
||||||
BOOL bRet = TRUE;
|
nativetouch::inject::inject_synthetic_touch({ touch.x, touch.y }, down);
|
||||||
const int contact_offset = 2;
|
|
||||||
|
|
||||||
memset(&contact, 0, sizeof(POINTER_TOUCH_INFO));
|
|
||||||
|
|
||||||
contact.pointerInfo.pointerType = PT_TOUCH;
|
|
||||||
contact.pointerInfo.pointerId = 0;
|
|
||||||
contact.pointerInfo.ptPixelLocation.x = tp.x;
|
|
||||||
contact.pointerInfo.ptPixelLocation.y = tp.y;
|
|
||||||
if (is_down) {
|
|
||||||
contact.pointerInfo.pointerFlags = POINTER_FLAG_DOWN | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT;
|
|
||||||
} else {
|
|
||||||
contact.pointerInfo.pointerFlags = POINTER_FLAG_UP;
|
|
||||||
}
|
|
||||||
|
|
||||||
contact.pointerInfo.dwTime = 0;
|
|
||||||
contact.pointerInfo.PerformanceCount = 0;
|
|
||||||
|
|
||||||
contact.touchFlags = TOUCH_FLAG_NONE;
|
|
||||||
contact.touchMask = TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE;
|
|
||||||
contact.orientation = 0;
|
|
||||||
contact.pressure = 1024;
|
|
||||||
|
|
||||||
contact.rcContact.top = tp.y - contact_offset;
|
|
||||||
contact.rcContact.bottom = tp.y + contact_offset;
|
|
||||||
contact.rcContact.left = tp.x - contact_offset;
|
|
||||||
contact.rcContact.right = tp.x + contact_offset;
|
|
||||||
|
|
||||||
bRet = InjectTouchInput(1, &contact);
|
|
||||||
if (!bRet) {
|
|
||||||
log_warning("poke", "error injecting native touch {}: ({}, {}) error: {}", is_down ? "down" : "up", tp.x, tp.y, GetLastError());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void emulate_native_touch_points(std::vector<TouchPoint> *touch_points) {
|
|
||||||
int i = 0;
|
|
||||||
for (auto &touch : *touch_points) {
|
|
||||||
emulate_native_touch(touch, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear_native_touch_points(std::vector<TouchPoint> *touch_points) {
|
|
||||||
for (auto &touch : *touch_points) {
|
|
||||||
emulate_native_touch(touch, false);
|
|
||||||
}
|
|
||||||
touch_points->clear();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void clear_touch_points(std::vector<TouchPoint> *touch_points) {
|
|
||||||
std::vector<DWORD> touch_ids;
|
|
||||||
for (auto &touch : *touch_points) {
|
|
||||||
touch_ids.emplace_back(touch.id);
|
|
||||||
}
|
|
||||||
touch_remove_points(&touch_ids);
|
|
||||||
touch_points->clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void enable() {
|
void enable() {
|
||||||
@@ -198,11 +117,14 @@ namespace games::iidx::poke {
|
|||||||
if (THREAD)
|
if (THREAD)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!games::iidx::NATIVE_TOUCH) {
|
||||||
|
log_warning("poke", "keypad touch emulation requires native touch; disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// create new thread
|
// create new thread
|
||||||
THREAD_RUNNING = true;
|
THREAD_RUNNING = true;
|
||||||
THREAD = new std::thread([] {
|
THREAD = new std::thread([] {
|
||||||
timeutils::PreciseSleepTimer timer;
|
|
||||||
|
|
||||||
// log
|
// log
|
||||||
log_info("poke", "enabled");
|
log_info("poke", "enabled");
|
||||||
|
|
||||||
@@ -210,35 +132,13 @@ namespace games::iidx::poke {
|
|||||||
std::vector<TouchPoint> touch_points;
|
std::vector<TouchPoint> touch_points;
|
||||||
std::vector<uint16_t> last_keypad_state = {0, 0};
|
std::vector<uint16_t> last_keypad_state = {0, 0};
|
||||||
|
|
||||||
#if POKE_NATIVE_TOUCH
|
|
||||||
bool use_native = games::iidx::NATIVE_TOUCH;
|
|
||||||
|
|
||||||
if (use_native) {
|
|
||||||
initialize_native_touch_library();
|
|
||||||
|
|
||||||
if (pInitializeTouchInjection != nullptr) {
|
|
||||||
pInitializeTouchInjection(1, TOUCH_FEEDBACK_NONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
bool use_native = false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// set variable to false to stop
|
// set variable to false to stop
|
||||||
while (THREAD_RUNNING) {
|
while (THREAD_RUNNING) {
|
||||||
|
|
||||||
// clean up touch from last frame
|
// clean up touch from last frame
|
||||||
if (touch_points.size() > 0) {
|
if (touch_points.size() > 0) {
|
||||||
#if POKE_NATIVE_TOUCH
|
inject_native_touch_points(touch_points, false);
|
||||||
if (use_native) {
|
touch_points.clear();
|
||||||
clear_native_touch_points(&touch_points);
|
|
||||||
} else {
|
|
||||||
clear_touch_points(&touch_points);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
clear_touch_points(&touch_points);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int unit = 0; unit < 2; unit++) {
|
for (int unit = 0; unit < 2; unit++) {
|
||||||
@@ -263,32 +163,15 @@ namespace games::iidx::poke {
|
|||||||
|
|
||||||
float x = x_iter->second / 1920.0;
|
float x = x_iter->second / 1920.0;
|
||||||
float y = y_iter->second / 1080.0;
|
float y = y_iter->second / 1080.0;
|
||||||
if (use_native) {
|
if (GRAPHICS_WINDOWED) {
|
||||||
x *= rawinput::TOUCHSCREEN_RANGE_X;
|
if (SPICETOUCH_TOUCH_WIDTH <= 0 || SPICETOUCH_TOUCH_HEIGHT <= 0) {
|
||||||
y *= rawinput::TOUCHSCREEN_RANGE_Y;
|
continue;
|
||||||
} else if (GRAPHICS_IIDX_WSUB) {
|
|
||||||
// Scale to windowed subscreen
|
|
||||||
x *= GRAPHICS_WSUB_WIDTH;
|
|
||||||
y *= GRAPHICS_WSUB_HEIGHT;
|
|
||||||
} else if (GENERIC_SUB_WINDOW_FULLSIZE || !overlay::OVERLAY->get_active()) {
|
|
||||||
// Overlay is not present, scale to main screen
|
|
||||||
if (GRAPHICS_WINDOWED) {
|
|
||||||
x *= SPICETOUCH_TOUCH_WIDTH;
|
|
||||||
y *= SPICETOUCH_TOUCH_HEIGHT;
|
|
||||||
} else {
|
|
||||||
x *= ImGui::GetIO().DisplaySize.x;
|
|
||||||
y *= ImGui::GetIO().DisplaySize.y;
|
|
||||||
}
|
}
|
||||||
|
x = SPICETOUCH_TOUCH_X + x * SPICETOUCH_TOUCH_WIDTH;
|
||||||
|
y = SPICETOUCH_TOUCH_Y + y * SPICETOUCH_TOUCH_HEIGHT;
|
||||||
} else {
|
} else {
|
||||||
// Overlay subscreen
|
x = x_iter->second;
|
||||||
x = (GENERIC_SUB_WINDOW_X + x * GENERIC_SUB_WINDOW_WIDTH);
|
y = y_iter->second;
|
||||||
y = (GENERIC_SUB_WINDOW_Y + y * GENERIC_SUB_WINDOW_HEIGHT);
|
|
||||||
|
|
||||||
// Scale to window size ratio
|
|
||||||
if (GRAPHICS_WINDOWED) {
|
|
||||||
x *= SPICETOUCH_TOUCH_WIDTH / ImGui::GetIO().DisplaySize.x;
|
|
||||||
y *= SPICETOUCH_TOUCH_HEIGHT / ImGui::GetIO().DisplaySize.y;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchPoint tp {
|
TouchPoint tp {
|
||||||
@@ -309,19 +192,15 @@ namespace games::iidx::poke {
|
|||||||
} // for all units
|
} // for all units
|
||||||
|
|
||||||
if (touch_points.size() > 0) {
|
if (touch_points.size() > 0) {
|
||||||
#if POKE_NATIVE_TOUCH
|
inject_native_touch_points(touch_points, true);
|
||||||
if (use_native) {
|
|
||||||
emulate_native_touch_points(&touch_points);
|
|
||||||
} else {
|
|
||||||
touch_write_points(&touch_points);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
touch_write_points(&touch_points);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// slow down
|
// slow down
|
||||||
timer.sleep(50);
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!touch_points.empty()) {
|
||||||
|
inject_native_touch_points(touch_points, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ namespace games {
|
|||||||
static bool IO_INITIALIZED = false;
|
static bool IO_INITIALIZED = false;
|
||||||
static std::vector<std::string> games;
|
static std::vector<std::string> games;
|
||||||
static robin_hood::unordered_map<std::string, std::vector<Button> &> buttons;
|
static robin_hood::unordered_map<std::string, std::vector<Button> &> buttons;
|
||||||
|
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_modifiers;
|
||||||
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_keypads;
|
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_keypads;
|
||||||
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_overlay;
|
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_overlay;
|
||||||
static robin_hood::unordered_map<std::string, std::string> buttons_help;
|
static robin_hood::unordered_map<std::string, std::string> buttons_help;
|
||||||
@@ -132,6 +133,7 @@ namespace games {
|
|||||||
buttons_help.insert({ jb, jb::get_buttons_help() });
|
buttons_help.insert({ jb, jb::get_buttons_help() });
|
||||||
lights.insert({ jb, jb::get_lights() });
|
lights.insert({ jb, jb::get_lights() });
|
||||||
file_hints[jb].push_back({"jubeat.dll"});
|
file_hints[jb].push_back({"jubeat.dll"});
|
||||||
|
file_hints[jb].push_back({"jubeat2019.dll"});
|
||||||
|
|
||||||
// mga
|
// mga
|
||||||
const std::string mga("Metal Gear");
|
const std::string mga("Metal Gear");
|
||||||
@@ -365,6 +367,37 @@ namespace games {
|
|||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<Button> gen_buttons_modifiers(const std::string &game) {
|
||||||
|
auto modifier_buttons = GameAPI::Buttons::getButtons(game);
|
||||||
|
const std::vector<std::string> names {
|
||||||
|
"Modifier 1",
|
||||||
|
"Modifier 2",
|
||||||
|
"Modifier 3",
|
||||||
|
"Modifier 4",
|
||||||
|
};
|
||||||
|
modifier_buttons = GameAPI::Buttons::sortButtons(modifier_buttons, names);
|
||||||
|
for (auto &modifier : modifier_buttons) {
|
||||||
|
modifier.setModifierMask(0);
|
||||||
|
for (auto &alternative : modifier.getAlternatives()) {
|
||||||
|
alternative.setModifierMask(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modifier_buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Button> *get_buttons_modifiers(const std::string &game) {
|
||||||
|
initialize();
|
||||||
|
auto it = buttons_modifiers.find(game);
|
||||||
|
if (it == buttons_modifiers.end()) {
|
||||||
|
if (game.empty()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
buttons_modifiers[game] = gen_buttons_modifiers(game);
|
||||||
|
return &buttons_modifiers[game];
|
||||||
|
}
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
static std::vector<Button> gen_buttons_keypads(const std::string &game) {
|
static std::vector<Button> gen_buttons_keypads(const std::string &game) {
|
||||||
auto buttons = GameAPI::Buttons::getButtons(game);
|
auto buttons = GameAPI::Buttons::getButtons(game);
|
||||||
std::vector<std::string> names;
|
std::vector<std::string> names;
|
||||||
|
|||||||
@@ -5,6 +5,16 @@
|
|||||||
|
|
||||||
namespace games {
|
namespace games {
|
||||||
|
|
||||||
|
namespace ModifierButtons {
|
||||||
|
enum {
|
||||||
|
Modifier1,
|
||||||
|
Modifier2,
|
||||||
|
Modifier3,
|
||||||
|
Modifier4,
|
||||||
|
Size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
namespace OverlayButtons {
|
namespace OverlayButtons {
|
||||||
enum {
|
enum {
|
||||||
Screenshot,
|
Screenshot,
|
||||||
@@ -59,6 +69,7 @@ namespace games {
|
|||||||
|
|
||||||
const std::vector<std::string> &get_games();
|
const std::vector<std::string> &get_games();
|
||||||
std::vector<Button> *get_buttons(const std::string &game);
|
std::vector<Button> *get_buttons(const std::string &game);
|
||||||
|
std::vector<Button> *get_buttons_modifiers(const std::string &game);
|
||||||
std::string get_buttons_help(const std::string &game);
|
std::string get_buttons_help(const std::string &game);
|
||||||
std::string get_analogs_help(const std::string &game);
|
std::string get_analogs_help(const std::string &game);
|
||||||
std::vector<Button> *get_buttons_keypads(const std::string &game);
|
std::vector<Button> *get_buttons_keypads(const std::string &game);
|
||||||
|
|||||||
@@ -0,0 +1,336 @@
|
|||||||
|
#include "bi2x_hook.h"
|
||||||
|
|
||||||
|
#if SPICE64
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "games/io.h"
|
||||||
|
#include "games/jb/jb_touch.h"
|
||||||
|
#include "io.h"
|
||||||
|
#include "misc/eamuse.h"
|
||||||
|
#include "rawinput/rawinput.h"
|
||||||
|
#include "util/detour.h"
|
||||||
|
#include "util/logging.h"
|
||||||
|
|
||||||
|
namespace games::jb {
|
||||||
|
|
||||||
|
struct AIO_SCI_COMM {
|
||||||
|
std::array<uint8_t, 0x100> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_NMGR_IOB2;
|
||||||
|
|
||||||
|
struct AIO_NMGR_IOB2_VTABLE {
|
||||||
|
std::array<void *, 10> unused;
|
||||||
|
void (__fastcall *begin_manage)(AIO_NMGR_IOB2 *node_mgr);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_NMGR_IOB2 {
|
||||||
|
AIO_NMGR_IOB2_VTABLE *vtable;
|
||||||
|
std::array<uint8_t, 0x78> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_IOB2_BI2X_T44 {
|
||||||
|
std::array<uint8_t, 0x80> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_IOB2_BI2X_T44_DEVSTATUS {
|
||||||
|
std::array<uint8_t, 0x140> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_IOB2_BI2X_WRFIRM {
|
||||||
|
uint8_t data;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(AIO_NMGR_IOB2) == 0x80);
|
||||||
|
static_assert(sizeof(AIO_IOB2_BI2X_T44_DEVSTATUS) == 0x140);
|
||||||
|
|
||||||
|
using aioIob2Bi2xT44_Create_t = AIO_IOB2_BI2X_T44 *(__fastcall *)(AIO_NMGR_IOB2 *node_mgr,
|
||||||
|
uint32_t device_id);
|
||||||
|
using aioIob2Bi2xT44_GetDeviceStatus_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||||
|
AIO_IOB2_BI2X_T44_DEVSTATUS *status);
|
||||||
|
using aioIob2Bi2xT44_IoReset_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node, uint32_t reset);
|
||||||
|
using aioIob2Bi2xT44_SetWatchDogTimer_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node, uint8_t count);
|
||||||
|
using aioIob2Bi2xT44_ControlCoinBlocker_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||||
|
uint32_t slot, bool open);
|
||||||
|
using aioIob2Bi2xT44_AddCounter_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||||
|
uint32_t counter, uint32_t count);
|
||||||
|
using aioIob2Bi2xT44_SetIccrLed_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node, uint32_t color);
|
||||||
|
using aioIob2Bi2xT44_SetTapeLedData_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||||
|
uint32_t tape, const void *data);
|
||||||
|
using aioIob2Bi2x_OpenSciUsbCdc_t = AIO_SCI_COMM *(__fastcall *)(uint32_t serial_number);
|
||||||
|
using aioIob2Bi2x_CreateWriteFirmContext_t = AIO_IOB2_BI2X_WRFIRM *(__fastcall *)(
|
||||||
|
uint32_t serial_number, uint32_t iob_mask);
|
||||||
|
using aioIob2Bi2x_DestroyWriteFirmContext_t = void (__fastcall *)(AIO_IOB2_BI2X_WRFIRM *context);
|
||||||
|
using aioIob2Bi2x_WriteFirmGetState_t = int32_t (__fastcall *)(AIO_IOB2_BI2X_WRFIRM *context);
|
||||||
|
using aioIob2Bi2x_WriteFirmIsCompleted_t = bool (__fastcall *)(int32_t state);
|
||||||
|
using aioIob2Bi2x_WriteFirmIsError_t = bool (__fastcall *)(int32_t state);
|
||||||
|
using aioNMgrIob2_Create_t = AIO_NMGR_IOB2 *(__fastcall *)(AIO_SCI_COMM *sci, uint32_t mode);
|
||||||
|
using aioSci_Destroy_t = void (__fastcall *)(AIO_SCI_COMM *sci);
|
||||||
|
using aioNodeMgr_Destroy_t = void (__fastcall *)(AIO_NMGR_IOB2 *node_mgr);
|
||||||
|
using aioNodeCtl_Destroy_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node);
|
||||||
|
using aioNodeCtl_UpdateDevicesStatus_t = void (__fastcall *)();
|
||||||
|
|
||||||
|
static aioIob2Bi2xT44_Create_t aioIob2Bi2xT44_Create_orig = nullptr;
|
||||||
|
static aioIob2Bi2xT44_GetDeviceStatus_t aioIob2Bi2xT44_GetDeviceStatus_orig = nullptr;
|
||||||
|
static aioIob2Bi2xT44_IoReset_t aioIob2Bi2xT44_IoReset_orig = nullptr;
|
||||||
|
static aioIob2Bi2xT44_SetWatchDogTimer_t aioIob2Bi2xT44_SetWatchDogTimer_orig = nullptr;
|
||||||
|
static aioIob2Bi2xT44_ControlCoinBlocker_t aioIob2Bi2xT44_ControlCoinBlocker_orig = nullptr;
|
||||||
|
static aioIob2Bi2xT44_AddCounter_t aioIob2Bi2xT44_AddCounter_orig = nullptr;
|
||||||
|
static aioIob2Bi2xT44_SetIccrLed_t aioIob2Bi2xT44_SetIccrLed_orig = nullptr;
|
||||||
|
static aioIob2Bi2xT44_SetTapeLedData_t aioIob2Bi2xT44_SetTapeLedData_orig = nullptr;
|
||||||
|
static aioIob2Bi2x_OpenSciUsbCdc_t aioIob2Bi2x_OpenSciUsbCdc_orig = nullptr;
|
||||||
|
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
|
||||||
|
static aioIob2Bi2x_DestroyWriteFirmContext_t aioIob2Bi2x_DestroyWriteFirmContext_orig = nullptr;
|
||||||
|
static aioIob2Bi2x_WriteFirmGetState_t aioIob2Bi2x_WriteFirmGetState_orig = nullptr;
|
||||||
|
static aioIob2Bi2x_WriteFirmIsCompleted_t aioIob2Bi2x_WriteFirmIsCompleted_orig = nullptr;
|
||||||
|
static aioIob2Bi2x_WriteFirmIsError_t aioIob2Bi2x_WriteFirmIsError_orig = nullptr;
|
||||||
|
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
|
||||||
|
static aioSci_Destroy_t aioSci_Destroy_orig = nullptr;
|
||||||
|
static aioNodeMgr_Destroy_t aioNodeMgr_Destroy_orig = nullptr;
|
||||||
|
static aioNodeCtl_Destroy_t aioNodeCtl_Destroy_orig = nullptr;
|
||||||
|
static aioNodeCtl_UpdateDevicesStatus_t aioNodeCtl_UpdateDevicesStatus_orig = nullptr;
|
||||||
|
|
||||||
|
static AIO_SCI_COMM *aio_sci_comm = nullptr;
|
||||||
|
static AIO_NMGR_IOB2 *aio_node_mgr = nullptr;
|
||||||
|
static AIO_IOB2_BI2X_T44 *aio_t44 = nullptr;
|
||||||
|
static AIO_IOB2_BI2X_WRFIRM *aio_write_firm = nullptr;
|
||||||
|
static uint8_t input_counter = 0;
|
||||||
|
|
||||||
|
static void __fastcall aioNMgrIob_BeginManage(AIO_NMGR_IOB2 *) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static AIO_NMGR_IOB2_VTABLE aio_node_mgr_vtable {
|
||||||
|
{},
|
||||||
|
aioNMgrIob_BeginManage,
|
||||||
|
};
|
||||||
|
|
||||||
|
static AIO_SCI_COMM *__fastcall aioIob2Bi2x_OpenSciUsbCdc(uint32_t) {
|
||||||
|
aio_sci_comm = new AIO_SCI_COMM {};
|
||||||
|
return aio_sci_comm;
|
||||||
|
}
|
||||||
|
|
||||||
|
static AIO_NMGR_IOB2 *__fastcall aioNMgrIob2_Create(AIO_SCI_COMM *sci, uint32_t mode) {
|
||||||
|
if (sci != aio_sci_comm) {
|
||||||
|
return aioNMgrIob2_Create_orig(sci, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
aio_node_mgr = new AIO_NMGR_IOB2 {};
|
||||||
|
aio_node_mgr->vtable = &aio_node_mgr_vtable;
|
||||||
|
return aio_node_mgr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static AIO_IOB2_BI2X_T44 *__fastcall aioIob2Bi2xT44_Create(
|
||||||
|
AIO_NMGR_IOB2 *node_mgr, uint32_t device_id) {
|
||||||
|
if (node_mgr != aio_node_mgr) {
|
||||||
|
return aioIob2Bi2xT44_Create_orig(node_mgr, device_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("jb::bi2x", "T44 node created");
|
||||||
|
aio_t44 = new AIO_IOB2_BI2X_T44 {};
|
||||||
|
return aio_t44;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2xT44_GetDeviceStatus(
|
||||||
|
AIO_IOB2_BI2X_T44 *node, AIO_IOB2_BI2X_T44_DEVSTATUS *status) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioIob2Bi2xT44_GetDeviceStatus_orig(node, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
RI_MGR->devices_flush_output();
|
||||||
|
std::memset(status, 0, sizeof(*status));
|
||||||
|
|
||||||
|
status->data[0x00] = input_counter;
|
||||||
|
status->data[0x03] = input_counter++;
|
||||||
|
status->data[0x0A] = static_cast<uint8_t>(eamuse_coin_get_stock());
|
||||||
|
|
||||||
|
games::jb::touch_update();
|
||||||
|
const auto touched = games::jb::touch_state();
|
||||||
|
auto &buttons = get_buttons();
|
||||||
|
|
||||||
|
status->data[0x04] = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]) ? 0xFF : 0;
|
||||||
|
status->data[0x05] = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]) ? 0xFF : 0;
|
||||||
|
status->data[0x06] = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]) ? 0xFF : 0;
|
||||||
|
|
||||||
|
static constexpr size_t PANEL_ORDER[16] = {
|
||||||
|
3, 7, 11, 15,
|
||||||
|
2, 6, 10, 14,
|
||||||
|
1, 5, 9, 13,
|
||||||
|
0, 4, 8, 12,
|
||||||
|
};
|
||||||
|
for (size_t status_index = 0; status_index < std::size(PANEL_ORDER); status_index++) {
|
||||||
|
const auto panel_index = PANEL_ORDER[status_index];
|
||||||
|
if (touched[panel_index] || GameAPI::Buttons::getState(
|
||||||
|
RI_MGR, buttons[Buttons::Button1 + panel_index])) {
|
||||||
|
status->data[0x0F + status_index] = 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2xT44_IoReset(AIO_IOB2_BI2X_T44 *node, uint32_t reset) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioIob2Bi2xT44_IoReset_orig(node, reset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2xT44_SetWatchDogTimer(AIO_IOB2_BI2X_T44 *node, uint8_t count) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioIob2Bi2xT44_SetWatchDogTimer_orig(node, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2xT44_ControlCoinBlocker(
|
||||||
|
AIO_IOB2_BI2X_T44 *node, uint32_t slot, bool open) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioIob2Bi2xT44_ControlCoinBlocker_orig(node, slot, open);
|
||||||
|
}
|
||||||
|
|
||||||
|
eamuse_coin_set_block(!open);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2xT44_AddCounter(
|
||||||
|
AIO_IOB2_BI2X_T44 *node, uint32_t counter, uint32_t count) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioIob2Bi2xT44_AddCounter_orig(node, counter, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2xT44_SetIccrLed(AIO_IOB2_BI2X_T44 *node, uint32_t color) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioIob2Bi2xT44_SetIccrLed_orig(node, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2xT44_SetTapeLedData(
|
||||||
|
AIO_IOB2_BI2X_T44 *node, uint32_t tape, const void *data) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioIob2Bi2xT44_SetTapeLedData_orig(node, tape, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static AIO_IOB2_BI2X_WRFIRM *__fastcall aioIob2Bi2x_CreateWriteFirmContext(uint32_t, uint32_t) {
|
||||||
|
aio_write_firm = new AIO_IOB2_BI2X_WRFIRM {};
|
||||||
|
return aio_write_firm;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2x_DestroyWriteFirmContext(AIO_IOB2_BI2X_WRFIRM *context) {
|
||||||
|
if (context != aio_write_firm) {
|
||||||
|
return aioIob2Bi2x_DestroyWriteFirmContext_orig(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete aio_write_firm;
|
||||||
|
aio_write_firm = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t __fastcall aioIob2Bi2x_WriteFirmGetState(AIO_IOB2_BI2X_WRFIRM *context) {
|
||||||
|
if (context != aio_write_firm) {
|
||||||
|
return aioIob2Bi2x_WriteFirmGetState_orig(context);
|
||||||
|
}
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool __fastcall aioIob2Bi2x_WriteFirmIsCompleted(int32_t state) {
|
||||||
|
if (aio_write_firm) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return aioIob2Bi2x_WriteFirmIsCompleted_orig(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool __fastcall aioIob2Bi2x_WriteFirmIsError(int32_t state) {
|
||||||
|
if (aio_write_firm) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return aioIob2Bi2x_WriteFirmIsError_orig(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioSci_Destroy(AIO_SCI_COMM *sci) {
|
||||||
|
if (sci != aio_sci_comm) {
|
||||||
|
return aioSci_Destroy_orig(sci);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete aio_sci_comm;
|
||||||
|
aio_sci_comm = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB2 *node_mgr) {
|
||||||
|
if (node_mgr != aio_node_mgr) {
|
||||||
|
return aioNodeMgr_Destroy_orig(node_mgr);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete aio_node_mgr;
|
||||||
|
aio_node_mgr = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioNodeCtl_Destroy(AIO_IOB2_BI2X_T44 *node) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioNodeCtl_Destroy_orig(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete aio_t44;
|
||||||
|
aio_t44 = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioNodeCtl_UpdateDevicesStatus() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void bi2x_hook_init() {
|
||||||
|
static bool initialized = false;
|
||||||
|
if (initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
log_info("jb::bi2x", "initializing T44 hooks");
|
||||||
|
|
||||||
|
const auto libaio_iob2_video = "libaio-iob2_video.dll";
|
||||||
|
detour::trampoline_try(libaio_iob2_video, "aioIob2Bi2xT44_Create",
|
||||||
|
aioIob2Bi2xT44_Create, &aioIob2Bi2xT44_Create_orig);
|
||||||
|
detour::trampoline_try(libaio_iob2_video,
|
||||||
|
"?GetDeviceStatus@AIO_IOB2_BI2X_T44@@QEBAXAEAUDEVSTATUS@1@@Z",
|
||||||
|
aioIob2Bi2xT44_GetDeviceStatus, &aioIob2Bi2xT44_GetDeviceStatus_orig);
|
||||||
|
detour::trampoline_try(libaio_iob2_video, "?IoReset@AIO_IOB2_BI2X_T44@@QEAAXI@Z",
|
||||||
|
aioIob2Bi2xT44_IoReset, &aioIob2Bi2xT44_IoReset_orig);
|
||||||
|
detour::trampoline_try(libaio_iob2_video,
|
||||||
|
"?SetWatchDogTimer@AIO_IOB2_BI2X_T44@@QEAAXE@Z",
|
||||||
|
aioIob2Bi2xT44_SetWatchDogTimer, &aioIob2Bi2xT44_SetWatchDogTimer_orig);
|
||||||
|
detour::trampoline_try(libaio_iob2_video,
|
||||||
|
"?ControlCoinBlocker@AIO_IOB2_BI2X_T44@@QEAAXI_N@Z",
|
||||||
|
aioIob2Bi2xT44_ControlCoinBlocker, &aioIob2Bi2xT44_ControlCoinBlocker_orig);
|
||||||
|
detour::trampoline_try(libaio_iob2_video, "?AddCounter@AIO_IOB2_BI2X_T44@@QEAAXII@Z",
|
||||||
|
aioIob2Bi2xT44_AddCounter, &aioIob2Bi2xT44_AddCounter_orig);
|
||||||
|
detour::trampoline_try(libaio_iob2_video, "?SetIccrLed@AIO_IOB2_BI2X_T44@@QEAAXI@Z",
|
||||||
|
aioIob2Bi2xT44_SetIccrLed, &aioIob2Bi2xT44_SetIccrLed_orig);
|
||||||
|
detour::trampoline_try(libaio_iob2_video,
|
||||||
|
"?SetTapeLedData@AIO_IOB2_BI2X_T44@@QEAAXIPEBX@Z",
|
||||||
|
aioIob2Bi2xT44_SetTapeLedData, &aioIob2Bi2xT44_SetTapeLedData_orig);
|
||||||
|
|
||||||
|
const auto libaio_iob = "libaio-iob.dll";
|
||||||
|
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_OpenSciUsbCdc",
|
||||||
|
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
||||||
|
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_CreateWriteFirmContext",
|
||||||
|
aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig);
|
||||||
|
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_DestroyWriteFirmContext",
|
||||||
|
aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig);
|
||||||
|
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_WriteFirmGetState",
|
||||||
|
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
|
||||||
|
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_WriteFirmIsCompleted",
|
||||||
|
aioIob2Bi2x_WriteFirmIsCompleted, &aioIob2Bi2x_WriteFirmIsCompleted_orig);
|
||||||
|
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_WriteFirmIsError",
|
||||||
|
aioIob2Bi2x_WriteFirmIsError, &aioIob2Bi2x_WriteFirmIsError_orig);
|
||||||
|
detour::trampoline_try(libaio_iob, "aioNMgrIob2_Create",
|
||||||
|
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||||
|
|
||||||
|
const auto libaio = "libaio.dll";
|
||||||
|
detour::trampoline_try(libaio, "aioSci_Destroy", aioSci_Destroy, &aioSci_Destroy_orig);
|
||||||
|
detour::trampoline_try(libaio, "aioNodeMgr_Destroy",
|
||||||
|
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
|
||||||
|
detour::trampoline_try(libaio, "aioNodeCtl_Destroy",
|
||||||
|
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
|
||||||
|
detour::trampoline_try(libaio, "aioNodeCtl_UpdateDevicesStatus",
|
||||||
|
aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if SPICE64
|
||||||
|
|
||||||
|
namespace games::jb {
|
||||||
|
void bi2x_hook_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "avs/game.h"
|
||||||
|
#include "bi2x_hook.h"
|
||||||
#include "cfg/configurator.h"
|
#include "cfg/configurator.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
@@ -10,6 +12,8 @@
|
|||||||
|
|
||||||
namespace games::jb {
|
namespace games::jb {
|
||||||
|
|
||||||
|
#if !SPICE64
|
||||||
|
|
||||||
// fixes "IP ADDR CHANGE" errors with unusual network setups (e.g. a VPN)
|
// fixes "IP ADDR CHANGE" errors with unusual network setups (e.g. a VPN)
|
||||||
static BOOL __stdcall network_addr_is_changed() {
|
static BOOL __stdcall network_addr_is_changed() {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -31,6 +35,8 @@ namespace games::jb {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
JBGame::JBGame() : Game("Jubeat") {
|
JBGame::JBGame() : Game("Jubeat") {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,8 +66,18 @@ namespace games::jb {
|
|||||||
void JBGame::attach() {
|
void JBGame::attach() {
|
||||||
Game::attach();
|
Game::attach();
|
||||||
|
|
||||||
|
#if SPICE64
|
||||||
|
if (avs::game::DLL_NAME == "jubeat2019.dll") {
|
||||||
|
libutils::load_library("libaio.dll");
|
||||||
|
libutils::load_library("libaio-iob.dll");
|
||||||
|
libutils::load_library("libaio-iob2_video.dll");
|
||||||
|
bi2x_hook_init();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
touch_attach();
|
touch_attach();
|
||||||
|
|
||||||
|
#if !SPICE64
|
||||||
// enable debug logging of gftools
|
// enable debug logging of gftools
|
||||||
HMODULE gftools = libutils::try_module("gftools.dll");
|
HMODULE gftools = libutils::try_module("gftools.dll");
|
||||||
detour::inline_hook((void *) GFDbgSetReportFunc, libutils::try_proc(
|
detour::inline_hook((void *) GFDbgSetReportFunc, libutils::try_proc(
|
||||||
@@ -75,6 +91,8 @@ namespace games::jb {
|
|||||||
network, "network_get_network_check_info"));
|
network, "network_get_network_check_info"));
|
||||||
detour::inline_hook((void *) network_get_dhcp_result, libutils::try_proc(
|
detour::inline_hook((void *) network_get_dhcp_result, libutils::try_proc(
|
||||||
network, "network_get_dhcp_result"));
|
network, "network_get_dhcp_result"));
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void JBGame::detach() {
|
void JBGame::detach() {
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
// (6, 8) in landscape.
|
// (6, 8) in landscape.
|
||||||
#define JB_BUTTON_SIZE 160
|
#define JB_BUTTON_SIZE 160
|
||||||
#define JB_MAX_BUTTON_GAP 38
|
#define JB_MAX_BUTTON_GAP 38
|
||||||
|
#define JB_T44_BUTTON_SIZE 224
|
||||||
|
#define JB_T44_BUTTON_GAP 33
|
||||||
|
|
||||||
// improved and plus modes use this reach around each button. must be >= the
|
// improved and plus modes use this reach around each button. must be >= the
|
||||||
// diagonal half of the widest gap (~27px) so the grid centre still reaches a button.
|
// diagonal half of the widest gap (~27px) so the grid centre still reaches a button.
|
||||||
@@ -41,6 +43,7 @@ namespace games::jb {
|
|||||||
static std::atomic_bool TOUCH_ENABLE = false;
|
static std::atomic_bool TOUCH_ENABLE = false;
|
||||||
static bool TOUCH_ATTACHED = false;
|
static bool TOUCH_ATTACHED = false;
|
||||||
static bool IS_PORTRAIT = true;
|
static bool IS_PORTRAIT = true;
|
||||||
|
static bool IS_T44 = false;
|
||||||
static std::atomic_uint16_t TOUCH_STATE = 0;
|
static std::atomic_uint16_t TOUCH_STATE = 0;
|
||||||
|
|
||||||
// fixed-size contact view used by the debug overlay
|
// fixed-size contact view used by the debug overlay
|
||||||
@@ -85,35 +88,45 @@ namespace games::jb {
|
|||||||
// --- touch geometry ------------------------------------------------------
|
// --- touch geometry ------------------------------------------------------
|
||||||
// gaps between the four buttons along one axis (the middle gap is 1px wider)
|
// gaps between the four buttons along one axis (the middle gap is 1px wider)
|
||||||
static const int JB_BUTTON_GAPS[3] = { 37, JB_MAX_BUTTON_GAP, 37 };
|
static const int JB_BUTTON_GAPS[3] = { 37, JB_MAX_BUTTON_GAP, 37 };
|
||||||
|
static const int JB_T44_BUTTON_GAPS[3] = {
|
||||||
|
JB_T44_BUTTON_GAP,
|
||||||
|
JB_T44_BUTTON_GAP,
|
||||||
|
JB_T44_BUTTON_GAP,
|
||||||
|
};
|
||||||
|
|
||||||
struct AxisGeometry {
|
struct AxisGeometry {
|
||||||
|
int size;
|
||||||
int button[4]; // left/top edge of each button
|
int button[4]; // left/top edge of each button
|
||||||
};
|
};
|
||||||
|
|
||||||
// left/top edges of the four buttons along one axis, starting at `first`
|
// left/top edges of the four buttons along one axis, starting at `first`
|
||||||
static AxisGeometry axis_geometry(int first) {
|
static AxisGeometry axis_geometry(int first, int size, const int gaps[3]) {
|
||||||
AxisGeometry g {};
|
AxisGeometry g {};
|
||||||
|
g.size = size;
|
||||||
g.button[0] = first;
|
g.button[0] = first;
|
||||||
for (int i = 1; i < 4; i++) {
|
for (int i = 1; i < 4; i++) {
|
||||||
g.button[i] = g.button[i - 1] + JB_BUTTON_SIZE + JB_BUTTON_GAPS[i - 1];
|
g.button[i] = g.button[i - 1] + size + gaps[i - 1];
|
||||||
}
|
}
|
||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
|
|
||||||
// button edges for the current orientation
|
// button edges for the current orientation
|
||||||
static void touch_geometry(AxisGeometry &gx, AxisGeometry &gy) {
|
static void touch_geometry(AxisGeometry &gx, AxisGeometry &gy) {
|
||||||
if (IS_PORTRAIT) {
|
if (IS_T44) {
|
||||||
gx = axis_geometry(8);
|
gx = axis_geometry(37, JB_T44_BUTTON_SIZE, JB_T44_BUTTON_GAPS);
|
||||||
gy = axis_geometry(602);
|
gy = axis_geometry(864, JB_T44_BUTTON_SIZE, JB_T44_BUTTON_GAPS);
|
||||||
|
} else if (IS_PORTRAIT) {
|
||||||
|
gx = axis_geometry(8, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||||
|
gy = axis_geometry(602, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||||
} else {
|
} else {
|
||||||
gx = axis_geometry(6);
|
gx = axis_geometry(6, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||||
gy = axis_geometry(8);
|
gy = axis_geometry(8, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// distance from `p` to a button along one axis (0 when inside)
|
// distance from `p` to a button along one axis (0 when inside)
|
||||||
static int axis_distance(int p, int button) {
|
static int axis_distance(int p, int button, int size) {
|
||||||
int end = button + JB_BUTTON_SIZE - 1;
|
int end = button + size - 1;
|
||||||
if (p < button) {
|
if (p < button) {
|
||||||
return button - p;
|
return button - p;
|
||||||
}
|
}
|
||||||
@@ -131,8 +144,8 @@ namespace games::jb {
|
|||||||
int best_dist = 0;
|
int best_dist = 0;
|
||||||
for (int r = 0; r < 4; r++) {
|
for (int r = 0; r < 4; r++) {
|
||||||
for (int c = 0; c < 4; c++) {
|
for (int c = 0; c < 4; c++) {
|
||||||
int dx = axis_distance(px, gx.button[c]);
|
int dx = axis_distance(px, gx.button[c], gx.size);
|
||||||
int dy = axis_distance(py, gy.button[r]);
|
int dy = axis_distance(py, gy.button[r], gy.size);
|
||||||
int dist = dx * dx + dy * dy;
|
int dist = dx * dx + dy * dy;
|
||||||
if (dist <= radius * radius && (best_index < 0 || dist < best_dist)) {
|
if (dist <= radius * radius && (best_index < 0 || dist < best_dist)) {
|
||||||
best_dist = dist;
|
best_dist = dist;
|
||||||
@@ -145,7 +158,8 @@ namespace games::jb {
|
|||||||
|
|
||||||
// detection reach for the current algorithm (0 = register only inside a button)
|
// detection reach for the current algorithm (0 = register only inside a button)
|
||||||
static int touch_radius() {
|
static int touch_radius() {
|
||||||
return TOUCH_ALGORITHM == AcAccurate ? 0 : JB_TOUCH_RADIUS;
|
return TOUCH_ALGORITHM == AcAccurate || (IS_T44 && TOUCH_ALGORITHM == Legacy) ?
|
||||||
|
0 : JB_TOUCH_RADIUS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// mark the buttons a touch at (px, py) hits: only the nearest within `radius`, or
|
// mark the buttons a touch at (px, py) hits: only the nearest within `radius`, or
|
||||||
@@ -162,8 +176,8 @@ namespace games::jb {
|
|||||||
}
|
}
|
||||||
for (int r = 0; r < 4; r++) {
|
for (int r = 0; r < 4; r++) {
|
||||||
for (int c = 0; c < 4; c++) {
|
for (int c = 0; c < 4; c++) {
|
||||||
int dx = axis_distance(px, gx.button[c]);
|
int dx = axis_distance(px, gx.button[c], gx.size);
|
||||||
int dy = axis_distance(py, gy.button[r]);
|
int dy = axis_distance(py, gy.button[r], gy.size);
|
||||||
if (dx * dx + dy * dy <= radius * radius) {
|
if (dx * dx + dy * dy <= radius * radius) {
|
||||||
state |= uint16_t(1) << (r * 4 + c);
|
state |= uint16_t(1) << (r * 4 + c);
|
||||||
}
|
}
|
||||||
@@ -184,11 +198,15 @@ namespace games::jb {
|
|||||||
// one-time touch window attach
|
// one-time touch window attach
|
||||||
if (!TOUCH_ATTACHED) {
|
if (!TOUCH_ATTACHED) {
|
||||||
|
|
||||||
|
IS_T44 = avs::game::is_model("T44");
|
||||||
|
IS_PORTRAIT = IS_T44 || avs::game::is_model("L44");
|
||||||
|
|
||||||
// find the game window: prefer the foreground window, else search by
|
// find the game window: prefer the foreground window, else search by
|
||||||
// title (the model name prefixes the window title in every version)
|
// title (T44 uses a fixed title instead of the model prefix)
|
||||||
|
const char *window_title = IS_T44 ? "jubeat 10 main" : avs::game::MODEL;
|
||||||
HWND wnd = GetForegroundWindow();
|
HWND wnd = GetForegroundWindow();
|
||||||
if (!string_begins_with(GetActiveWindowTitle(), avs::game::MODEL)) {
|
if (!string_begins_with(GetActiveWindowTitle(), window_title)) {
|
||||||
wnd = FindWindowBeginsWith(avs::game::MODEL);
|
wnd = FindWindowBeginsWith(window_title);
|
||||||
}
|
}
|
||||||
if (!wnd) {
|
if (!wnd) {
|
||||||
log_warning("jubeat", "could not find window handle for touch");
|
log_warning("jubeat", "could not find window handle for touch");
|
||||||
@@ -197,16 +215,13 @@ namespace games::jb {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// only the L44 model runs in portrait; set this before starting the
|
|
||||||
// touch-window thread so the renderer only observes the final value
|
|
||||||
IS_PORTRAIT = avs::game::is_model("L44");
|
|
||||||
|
|
||||||
log_info("jubeat", "using window handle for touch: {}", fmt::ptr(wnd));
|
log_info("jubeat", "using window handle for touch: {}", fmt::ptr(wnd));
|
||||||
touch_create_wnd(wnd, true);
|
|
||||||
|
|
||||||
// let the rawinput stack correct the aspect ratio
|
// let the rawinput stack correct the aspect ratio
|
||||||
::rawinput::touch::ASPECT_COMPENSATION_GAME = true;
|
::rawinput::touch::ASPECT_COMPENSATION_GAME = true;
|
||||||
|
|
||||||
|
touch_create_wnd(wnd, true);
|
||||||
|
|
||||||
if (GRAPHICS_SHOW_CURSOR) {
|
if (GRAPHICS_SHOW_CURSOR) {
|
||||||
ShowCursor(TRUE);
|
ShowCursor(TRUE);
|
||||||
}
|
}
|
||||||
@@ -231,7 +246,7 @@ namespace games::jb {
|
|||||||
return !touch_matured(tp, now_ms, threshold_ms);
|
return !touch_matured(tp, now_ms, threshold_ms);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (TOUCH_ALGORITHM == Legacy) {
|
if (TOUCH_ALGORITHM == Legacy && !IS_T44) {
|
||||||
|
|
||||||
// legacy: evenly divide the play area into a 4x4 grid
|
// legacy: evenly divide the play area into a 4x4 grid
|
||||||
auto offset = IS_PORTRAIT ? 580 : 0;
|
auto offset = IS_PORTRAIT ? 580 : 0;
|
||||||
@@ -310,7 +325,7 @@ namespace games::jb {
|
|||||||
for (int c = 0; c < 4; c++) {
|
for (int c = 0; c < 4; c++) {
|
||||||
cells[r * 4 + c] = {
|
cells[r * 4 + c] = {
|
||||||
gx.button[c], gy.button[r],
|
gx.button[c], gy.button[r],
|
||||||
gx.button[c] + JB_BUTTON_SIZE, gy.button[r] + JB_BUTTON_SIZE
|
gx.button[c] + gx.size, gy.button[r] + gy.size
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -351,8 +366,8 @@ namespace games::jb {
|
|||||||
const AxisGeometry &gx, const AxisGeometry &gy, int arc_radius) {
|
const AxisGeometry &gx, const AxisGeometry &gy, int arc_radius) {
|
||||||
int c = index % 4;
|
int c = index % 4;
|
||||||
int r = index / 4;
|
int r = index / 4;
|
||||||
double mid = std::atan2((gy.button[r] + JB_BUTTON_SIZE / 2) - py,
|
double mid = std::atan2((gy.button[r] + gy.size / 2) - py,
|
||||||
(gx.button[c] + JB_BUTTON_SIZE / 2) - px);
|
(gx.button[c] + gx.size / 2) - px);
|
||||||
const double quarter = 3.14159265358979323846 / 2.0;
|
const double quarter = 3.14159265358979323846 / 2.0;
|
||||||
const int segments = 16;
|
const int segments = 16;
|
||||||
POINT arc[segments + 1];
|
POINT arc[segments + 1];
|
||||||
@@ -424,7 +439,7 @@ namespace games::jb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// legacy divides the field evenly; the other algorithms use button squares
|
// legacy divides the field evenly; the other algorithms use button squares
|
||||||
bool legacy = (TOUCH_ALGORITHM == Legacy);
|
bool legacy = TOUCH_ALGORITHM == Legacy && !IS_T44;
|
||||||
AxisGeometry gx {}, gy {};
|
AxisGeometry gx {}, gy {};
|
||||||
if (!legacy) {
|
if (!legacy) {
|
||||||
touch_geometry(gx, gy);
|
touch_geometry(gx, gy);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "util/execexe.h"
|
#include "util/execexe.h"
|
||||||
#include "acioemu/handle.h"
|
#include "acioemu/handle.h"
|
||||||
#include "misc/wintouchemu.h"
|
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
#include "bi2a_hook.h"
|
#include "bi2a_hook.h"
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
// enable touch functions - set version to windows 7
|
|
||||||
// mingw otherwise doesn't load touch stuff
|
|
||||||
#define _WIN32_WINNT 0x0601
|
|
||||||
|
|
||||||
#include "nost.h"
|
#include "nost.h"
|
||||||
#include "poke.h"
|
#include "poke.h"
|
||||||
|
#include "touch_mode.h"
|
||||||
#include "hooks/setupapihook.h"
|
#include "hooks/setupapihook.h"
|
||||||
#include "misc/wintouchemu.h"
|
#include "misc/wintouchemu.h"
|
||||||
|
#include "touch/native/nativetouchhook.h"
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
|
|
||||||
namespace games::nost {
|
namespace games::nost {
|
||||||
|
|
||||||
bool ENABLE_POKE = false;
|
bool ENABLE_POKE = false;
|
||||||
|
bool ENABLE_TOUCH_MODE = false;
|
||||||
|
|
||||||
NostGame::NostGame() : Game("Nostalgia") {
|
NostGame::NostGame() : Game("Nostalgia") {
|
||||||
}
|
}
|
||||||
@@ -43,9 +42,16 @@ namespace games::nost {
|
|||||||
setupapihook_init(avs::game::DLL_INSTANCE);
|
setupapihook_init(avs::game::DLL_INSTANCE);
|
||||||
setupapihook_add(touch_settings);
|
setupapihook_add(touch_settings);
|
||||||
|
|
||||||
// custom touch
|
const auto native_touch_ready = !wintouchemu::FORCE &&
|
||||||
// nostalgia crashes if you inject touch events too early
|
nativetouch::hook(avs::game::DLL_INSTANCE);
|
||||||
wintouchemu::hook("nostalgia", avs::game::DLL_INSTANCE, 20);
|
if (!native_touch_ready) {
|
||||||
|
wintouchemu::FORCE = true;
|
||||||
|
wintouchemu::hook("nostalgia", avs::game::DLL_INSTANCE, 20);
|
||||||
|
} else {
|
||||||
|
if (ENABLE_TOUCH_MODE) {
|
||||||
|
touch_mode::enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NostGame::post_attach() {
|
void NostGame::post_attach() {
|
||||||
@@ -55,6 +61,9 @@ namespace games::nost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NostGame::detach() {
|
void NostGame::detach() {
|
||||||
|
if (ENABLE_TOUCH_MODE) {
|
||||||
|
touch_mode::disable();
|
||||||
|
}
|
||||||
if (ENABLE_POKE) {
|
if (ENABLE_POKE) {
|
||||||
poke::disable();
|
poke::disable();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
namespace games::nost {
|
namespace games::nost {
|
||||||
|
|
||||||
extern bool ENABLE_POKE;
|
extern bool ENABLE_POKE;
|
||||||
|
extern bool ENABLE_TOUCH_MODE;
|
||||||
|
|
||||||
class NostGame : public games::Game {
|
class NostGame : public games::Game {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
#include "poke.h"
|
#include "poke.h"
|
||||||
|
|
||||||
#include <thread>
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "games/nost/io.h"
|
#include "games/nost/io.h"
|
||||||
#include "cfg/api.h"
|
#include "cfg/api.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "touch/touch.h"
|
#include "touch/native/inject.h"
|
||||||
|
#include "touch/native/nativetouchhook.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/precise_timer.h"
|
|
||||||
|
|
||||||
namespace games::nost::poke {
|
namespace games::nost::poke {
|
||||||
|
|
||||||
static std::thread *THREAD = nullptr;
|
static std::thread *THREAD = nullptr;
|
||||||
static volatile bool THREAD_RUNNING = false;
|
static std::atomic<bool> THREAD_RUNNING { false };
|
||||||
|
|
||||||
static const std::unordered_map<int, std::pair<LONG, LONG>> NOST_POKE_NUM {
|
// positions use game-client pixels, matching the legacy client-sized touch window
|
||||||
|
static const std::unordered_map<int, std::pair<LONG, LONG>> NOST_POKE_KEYPAD {
|
||||||
{EAM_IO_KEYPAD_0, {760, 440}},
|
{EAM_IO_KEYPAD_0, {760, 440}},
|
||||||
{EAM_IO_KEYPAD_1, {760, 385}},
|
{EAM_IO_KEYPAD_1, {760, 385}},
|
||||||
{EAM_IO_KEYPAD_2, {820, 385}},
|
{EAM_IO_KEYPAD_2, {820, 385}},
|
||||||
@@ -31,7 +34,8 @@ namespace games::nost::poke {
|
|||||||
{EAM_IO_KEYPAD_DECIMAL, {880, 440}} // also erase button
|
{EAM_IO_KEYPAD_DECIMAL, {880, 440}} // also erase button
|
||||||
};
|
};
|
||||||
|
|
||||||
static const std::unordered_map<games::nost::Buttons::Button, std::pair<LONG, LONG>> NOST_POKE {
|
static const std::unordered_map<
|
||||||
|
games::nost::Buttons::Button, std::pair<LONG, LONG>> NOST_POKE_BUTTONS {
|
||||||
{games::nost::Buttons::Button::PokeConfirm, {1100, 525}},
|
{games::nost::Buttons::Button::PokeConfirm, {1100, 525}},
|
||||||
{games::nost::Buttons::Button::PokeBack, {340, 100}},
|
{games::nost::Buttons::Button::PokeBack, {340, 100}},
|
||||||
{games::nost::Buttons::Button::PokeSong1, {450, 190}},
|
{games::nost::Buttons::Button::PokeSong1, {450, 190}},
|
||||||
@@ -46,13 +50,8 @@ namespace games::nost::poke {
|
|||||||
{games::nost::Buttons::Button::PokeDifficulty4, {820, 490}},
|
{games::nost::Buttons::Button::PokeDifficulty4, {820, 490}},
|
||||||
};
|
};
|
||||||
|
|
||||||
void clear_touch_points(std::vector<TouchPoint> *touch_points) {
|
static bool inject_poke_position(POINT position, bool down) {
|
||||||
std::vector<DWORD> touch_ids;
|
return nativetouch::inject::inject_synthetic_touch(position, down);
|
||||||
for (auto &touch : *touch_points) {
|
|
||||||
touch_ids.emplace_back(touch.id);
|
|
||||||
}
|
|
||||||
touch_remove_points(&touch_ids);
|
|
||||||
touch_points->clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void enable() {
|
void enable() {
|
||||||
@@ -61,126 +60,143 @@ namespace games::nost::poke {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!nativetouch::is_hooked()) {
|
||||||
|
log_warning("poke", "Nostalgia poke requires native touch; disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// create new thread
|
// create new thread
|
||||||
THREAD_RUNNING = true;
|
THREAD_RUNNING = true;
|
||||||
THREAD = new std::thread([] {
|
THREAD = new std::thread([] {
|
||||||
timeutils::PreciseSleepTimer timer;
|
|
||||||
const DWORD touch_id = (DWORD)(0xFFFFFFFE);
|
|
||||||
|
|
||||||
const int swipe_anim_total_frames = 6;
|
const int swipe_anim_total_frames = 6;
|
||||||
const int swipe_anim_y = 300;
|
const int swipe_anim_y = 300;
|
||||||
|
|
||||||
const int swipe_next_page_x_begin = 1120;
|
const int swipe_next_page_x_begin = 1120;
|
||||||
const int swipe_next_page_x_end = 1120-120;
|
const int swipe_next_page_x_end = 1120 - 120;
|
||||||
|
|
||||||
const int swipe_prev_page_x_begin = 280-120;
|
const int swipe_prev_page_x_begin = 280 - 120;
|
||||||
const int swipe_prev_page_x_end = 280;
|
const int swipe_prev_page_x_end = 280;
|
||||||
|
|
||||||
int next_page_anim_index = -1;
|
int next_page_anim_index = -1;
|
||||||
int prev_page_anim_index = -1;
|
int prev_page_anim_index = -1;
|
||||||
std::vector<TouchPoint> touch_points;
|
bool contact_active = false;
|
||||||
|
bool release_pending = false;
|
||||||
bool touch_release = false;
|
POINT last_position {};
|
||||||
|
std::unordered_map<games::nost::Buttons::Button, bool> button_states;
|
||||||
|
uint16_t last_keypad_state = 0;
|
||||||
|
|
||||||
// log
|
// log
|
||||||
log_info("poke", "enabled");
|
log_info("poke", "enabled");
|
||||||
|
|
||||||
// set variable to false to stop
|
// set variable to false to stop
|
||||||
while (THREAD_RUNNING) {
|
while (THREAD_RUNNING) {
|
||||||
// clean up touch from last frame
|
if (release_pending) {
|
||||||
if (!touch_points.empty()) {
|
if (contact_active) {
|
||||||
if (touch_release) {
|
inject_poke_position(last_position, false);
|
||||||
clear_touch_points(&touch_points);
|
|
||||||
touch_release = false;
|
|
||||||
} else {
|
|
||||||
touch_points.clear();
|
|
||||||
}
|
}
|
||||||
|
contact_active = false;
|
||||||
|
release_pending = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &buttons = games::nost::get_buttons();
|
auto &buttons = games::nost::get_buttons();
|
||||||
|
std::optional<POINT> touch_position;
|
||||||
|
bool release_after_touch = false;
|
||||||
|
|
||||||
|
const auto button_triggered = [&](games::nost::Buttons::Button button) {
|
||||||
|
const auto pressed =
|
||||||
|
GameAPI::Buttons::getState(RI_MGR, buttons.at(button));
|
||||||
|
const auto triggered = pressed && !button_states[button];
|
||||||
|
button_states[button] = pressed;
|
||||||
|
return triggered;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<POINT> triggered_touch_position;
|
||||||
|
for (const auto& it : NOST_POKE_BUTTONS) {
|
||||||
|
if (button_triggered(it.first) && !triggered_touch_position.has_value()) {
|
||||||
|
triggered_touch_position = POINT {
|
||||||
|
it.second.first,
|
||||||
|
it.second.second,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto next_triggered =
|
||||||
|
button_triggered(games::nost::Buttons::PokeNextPage);
|
||||||
|
const auto prev_triggered =
|
||||||
|
button_triggered(games::nost::Buttons::PokePrevPage);
|
||||||
|
|
||||||
|
const auto keypad_state = eamuse_get_keypad_state(0);
|
||||||
|
if (!triggered_touch_position.has_value()) {
|
||||||
|
for (const auto& it : NOST_POKE_KEYPAD) {
|
||||||
|
const auto mask = static_cast<uint16_t>(1 << it.first);
|
||||||
|
if ((keypad_state & mask) && !(last_keypad_state & mask)) {
|
||||||
|
triggered_touch_position = POINT {
|
||||||
|
it.second.first,
|
||||||
|
it.second.second,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_keypad_state = keypad_state;
|
||||||
|
|
||||||
if (0 <= next_page_anim_index) {
|
if (0 <= next_page_anim_index) {
|
||||||
const auto delta =
|
const auto delta =
|
||||||
(swipe_next_page_x_end - swipe_next_page_x_begin)
|
(swipe_next_page_x_end - swipe_next_page_x_begin)
|
||||||
* (swipe_anim_total_frames - next_page_anim_index) / swipe_anim_total_frames;
|
* (swipe_anim_total_frames - next_page_anim_index) / swipe_anim_total_frames;
|
||||||
|
|
||||||
TouchPoint tp {
|
touch_position = POINT {
|
||||||
.id = touch_id,
|
swipe_next_page_x_begin + delta,
|
||||||
.x = (LONG)swipe_next_page_x_begin + delta,
|
swipe_anim_y,
|
||||||
.y = (LONG)swipe_anim_y,
|
|
||||||
.mouse = true,
|
|
||||||
};
|
};
|
||||||
touch_points.emplace_back(tp);
|
|
||||||
next_page_anim_index--;
|
next_page_anim_index--;
|
||||||
if (next_page_anim_index < 0) {
|
if (next_page_anim_index < 0) {
|
||||||
touch_release = true;
|
release_after_touch = true;
|
||||||
}
|
}
|
||||||
} else if (0 <= prev_page_anim_index) {
|
} else if (0 <= prev_page_anim_index) {
|
||||||
const auto delta =
|
const auto delta =
|
||||||
(swipe_prev_page_x_end - swipe_prev_page_x_begin)
|
(swipe_prev_page_x_end - swipe_prev_page_x_begin)
|
||||||
* (swipe_anim_total_frames - prev_page_anim_index) / swipe_anim_total_frames;
|
* (swipe_anim_total_frames - prev_page_anim_index) / swipe_anim_total_frames;
|
||||||
|
|
||||||
TouchPoint tp {
|
touch_position = POINT {
|
||||||
.id = touch_id,
|
swipe_prev_page_x_begin + delta,
|
||||||
.x = (LONG)swipe_prev_page_x_begin + delta,
|
swipe_anim_y,
|
||||||
.y = (LONG)swipe_anim_y,
|
|
||||||
.mouse = true,
|
|
||||||
};
|
};
|
||||||
touch_points.emplace_back(tp);
|
|
||||||
prev_page_anim_index--;
|
prev_page_anim_index--;
|
||||||
if (prev_page_anim_index < 0) {
|
if (prev_page_anim_index < 0) {
|
||||||
touch_release = true;
|
release_after_touch = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const auto& it : NOST_POKE) {
|
touch_position = triggered_touch_position;
|
||||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(it.first))) {
|
release_after_touch = touch_position.has_value();
|
||||||
TouchPoint tp {
|
|
||||||
.id = touch_id,
|
|
||||||
.x = it.second.first,
|
|
||||||
.y = it.second.second,
|
|
||||||
.mouse = true,
|
|
||||||
};
|
|
||||||
touch_points.emplace_back(tp);
|
|
||||||
touch_release = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!touch_release) {
|
|
||||||
const auto state = eamuse_get_keypad_state(0);
|
|
||||||
if (state) {
|
|
||||||
for (const auto& it : NOST_POKE_NUM) {
|
|
||||||
if (state & (1 << it.first)) {
|
|
||||||
TouchPoint tp {
|
|
||||||
.id = touch_id,
|
|
||||||
.x = it.second.first,
|
|
||||||
.y = it.second.second,
|
|
||||||
.mouse = true,
|
|
||||||
};
|
|
||||||
touch_points.emplace_back(tp);
|
|
||||||
touch_release = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// start animations for next frame
|
// start animations for next frame
|
||||||
if (!touch_release) {
|
if (!touch_position.has_value()) {
|
||||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::PokeNextPage))) {
|
if (next_triggered) {
|
||||||
next_page_anim_index = swipe_anim_total_frames;
|
next_page_anim_index = swipe_anim_total_frames;
|
||||||
}
|
}
|
||||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::PokePrevPage))) {
|
|
||||||
|
if (prev_triggered) {
|
||||||
prev_page_anim_index = swipe_anim_total_frames;
|
prev_page_anim_index = swipe_anim_total_frames;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!touch_points.empty()) {
|
if (touch_position.has_value() &&
|
||||||
touch_write_points(&touch_points);
|
inject_poke_position(*touch_position, true)) {
|
||||||
|
contact_active = true;
|
||||||
|
last_position = *touch_position;
|
||||||
|
}
|
||||||
|
if (release_after_touch && contact_active) {
|
||||||
|
release_pending = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// slow down
|
// slow down
|
||||||
timer.sleep(30);
|
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contact_active) {
|
||||||
|
inject_poke_position(last_position, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|||||||
@@ -0,0 +1,233 @@
|
|||||||
|
#include "touch_mode.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "touch/native/nativetouchhook.h"
|
||||||
|
#include "util/logging.h"
|
||||||
|
|
||||||
|
namespace games::nost::touch_mode {
|
||||||
|
|
||||||
|
// native contact positions feed piano input directly. native events reach the game
|
||||||
|
// in nav mode and are suppressed in piano mode. mode-button contacts are always
|
||||||
|
// suppressed and change that routing after release.
|
||||||
|
static constexpr LONG PIANO_LEFT_GAP = 11;
|
||||||
|
static constexpr LONG PIANO_RIGHT_GAP = 10;
|
||||||
|
static constexpr uint32_t PIANO_KEY_COUNT = 28;
|
||||||
|
|
||||||
|
struct TouchGeometry {
|
||||||
|
HWND window = nullptr;
|
||||||
|
RECT mode_button {};
|
||||||
|
LONG client_width = 0;
|
||||||
|
LONG client_height = 0;
|
||||||
|
|
||||||
|
bool valid() const {
|
||||||
|
return window != nullptr && client_width > 0 && client_height > 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NativeContact {
|
||||||
|
POINT position {};
|
||||||
|
|
||||||
|
// position contains game-client coordinates
|
||||||
|
bool client_position_valid = false;
|
||||||
|
|
||||||
|
// down began on the mode switch button; remains true through up
|
||||||
|
bool mode_button = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::atomic_bool accept_events { false };
|
||||||
|
static std::atomic<Mode> current_mode_state { Mode::Nav };
|
||||||
|
static std::mutex state_mutex;
|
||||||
|
static TouchGeometry touch_geometry;
|
||||||
|
|
||||||
|
// native contacts are kept by ID so each contact contributes exactly one position
|
||||||
|
static std::unordered_map<DWORD, NativeContact> active_contacts;
|
||||||
|
|
||||||
|
// a hardware button release requests one change after all contacts are released
|
||||||
|
static bool mode_change_pending = false;
|
||||||
|
|
||||||
|
static void reset_state_locked() {
|
||||||
|
current_mode_state.store(Mode::Nav, std::memory_order_release);
|
||||||
|
touch_geometry = {};
|
||||||
|
active_contacts.clear();
|
||||||
|
mode_change_pending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hardware contacts arrive in screen coordinates
|
||||||
|
static bool native_touch_in_button(const nativetouch::NativeTouchEvent &event) {
|
||||||
|
if (!touch_geometry.valid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
POINT position { event.x, event.y };
|
||||||
|
if (!ScreenToClient(touch_geometry.window, &position)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return PtInRect(&touch_geometry.mode_button, position) != FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool update_touch_state(const nativetouch::NativeTouchEvent &event) {
|
||||||
|
std::lock_guard<std::mutex> lock(state_mutex);
|
||||||
|
|
||||||
|
// first, process down / move events
|
||||||
|
if (event.down || event.move) {
|
||||||
|
auto contact = active_contacts.try_emplace(event.id).first;
|
||||||
|
|
||||||
|
// keep track of IDs that began as a down on the mode switch button
|
||||||
|
if (event.down) {
|
||||||
|
contact->second.mode_button = native_touch_in_button(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for valid position
|
||||||
|
POINT position { event.x, event.y };
|
||||||
|
if (touch_geometry.window != nullptr &&
|
||||||
|
ScreenToClient(touch_geometry.window, &position)) {
|
||||||
|
contact->second.position = position;
|
||||||
|
contact->second.client_position_valid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto contact = active_contacts.find(event.id);
|
||||||
|
const bool mode_button_contact = contact != active_contacts.end() &&
|
||||||
|
contact->second.mode_button;
|
||||||
|
|
||||||
|
// process up events
|
||||||
|
if (event.up) {
|
||||||
|
active_contacts.erase(event.id);
|
||||||
|
|
||||||
|
// if a contact that began down event on the mode switch button has
|
||||||
|
// been released, a mode switch is now pending
|
||||||
|
if (mode_button_contact) {
|
||||||
|
mode_change_pending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply the change on the final hardware up. switching earlier would
|
||||||
|
// split another contact's down and up events across different modes
|
||||||
|
if (mode_change_pending && active_contacts.empty()) {
|
||||||
|
mode_change_pending = false;
|
||||||
|
const auto next_mode = current_mode() == Mode::Nav ? Mode::Piano : Mode::Nav;
|
||||||
|
current_mode_state.store(next_mode, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mode_button_contact;
|
||||||
|
}
|
||||||
|
|
||||||
|
// install the Nostalgia-specific native touch interception
|
||||||
|
void enable() {
|
||||||
|
if (accept_events.exchange(true, std::memory_order_acq_rel)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(state_mutex);
|
||||||
|
reset_state_locked();
|
||||||
|
}
|
||||||
|
|
||||||
|
nativetouch::set_input_filter(filter_native_touch);
|
||||||
|
log_info("nost::touch", "enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
void disable() {
|
||||||
|
if (!accept_events.exchange(false, std::memory_order_acq_rel)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nativetouch::set_input_filter(nullptr);
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(state_mutex);
|
||||||
|
reset_state_locked();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enabled() {
|
||||||
|
return accept_events.load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
Mode current_mode() {
|
||||||
|
return current_mode_state.load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
// publish the rendered overlay button rectangle in game-client pixels
|
||||||
|
void publish_button_bounds(HWND window, const RECT &client_bounds) {
|
||||||
|
TouchGeometry next {};
|
||||||
|
RECT client_rect {};
|
||||||
|
if (window != nullptr && GetClientRect(window, &client_rect) &&
|
||||||
|
client_rect.right > 0 && client_rect.bottom > 0) {
|
||||||
|
next.window = window;
|
||||||
|
next.mode_button = client_bounds;
|
||||||
|
next.client_width = client_rect.right;
|
||||||
|
next.client_height = client_rect.bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(state_mutex);
|
||||||
|
touch_geometry = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the active 28-key piano bitfield for the PANB input update
|
||||||
|
uint32_t piano_key_state() {
|
||||||
|
if (!enabled() || current_mode() != Mode::Piano) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(state_mutex);
|
||||||
|
if (current_mode() != Mode::Piano || !touch_geometry.valid()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t state = 0;
|
||||||
|
for (const auto &contact : active_contacts) {
|
||||||
|
// invalid position or mode-button contact; ignore these contacts
|
||||||
|
if (!contact.second.client_position_valid || contact.second.mode_button) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &position = contact.second.position;
|
||||||
|
|
||||||
|
// outside the client area or on the mode button; ignore these contacts
|
||||||
|
if (position.x < 0 || position.x >= touch_geometry.client_width ||
|
||||||
|
position.y < 0 || position.y >= touch_geometry.client_height ||
|
||||||
|
PtInRect(&touch_geometry.mode_button, position)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// divide the inset width evenly; touches in either side gap clamp to
|
||||||
|
// the nearest outer key so the physical screen edges remain playable
|
||||||
|
const auto piano_width =
|
||||||
|
touch_geometry.client_width - PIANO_LEFT_GAP - PIANO_RIGHT_GAP;
|
||||||
|
uint32_t key = 0;
|
||||||
|
if (position.x >= touch_geometry.client_width - PIANO_RIGHT_GAP) {
|
||||||
|
key = PIANO_KEY_COUNT - 1;
|
||||||
|
} else if (position.x >= PIANO_LEFT_GAP && piano_width > 0) {
|
||||||
|
key = static_cast<uint32_t>(
|
||||||
|
(position.x - PIANO_LEFT_GAP) * PIANO_KEY_COUNT / piano_width);
|
||||||
|
}
|
||||||
|
state |= UINT32_C(1) << key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update native contacts and report whether this event should be hidden from the game
|
||||||
|
bool filter_native_touch(const nativetouch::NativeTouchEvent &event) {
|
||||||
|
// synthetic events are outside this hardware-only feature
|
||||||
|
if (!enabled() || event.synthetic) {
|
||||||
|
// false leaves the event visible to the game
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// snapshot routing before an up event can commit a pending mode switch
|
||||||
|
const bool piano_mode_before_update = current_mode() == Mode::Piano;
|
||||||
|
|
||||||
|
// update the contact lifetime and commit any pending switch when safe
|
||||||
|
const bool mode_button_contact = update_touch_state(event);
|
||||||
|
|
||||||
|
// hide every event in a contact that began on the mode switch button
|
||||||
|
if (mode_button_contact) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// piano mode consumes hardware events; nav mode forwards them to the game
|
||||||
|
return piano_mode_before_update;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "touch/native/nativetouchhook.h"
|
||||||
|
|
||||||
|
namespace games::nost::touch_mode {
|
||||||
|
|
||||||
|
// nav mode forwards contacts to the game; piano mode converts them into piano keys
|
||||||
|
enum class Mode {
|
||||||
|
Nav,
|
||||||
|
Piano,
|
||||||
|
};
|
||||||
|
|
||||||
|
void enable();
|
||||||
|
void disable();
|
||||||
|
bool enabled();
|
||||||
|
|
||||||
|
Mode current_mode();
|
||||||
|
|
||||||
|
void publish_button_bounds(HWND window, const RECT &client_bounds);
|
||||||
|
|
||||||
|
uint32_t piano_key_state();
|
||||||
|
|
||||||
|
bool filter_native_touch(const nativetouch::NativeTouchEvent &event);
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
#include "util/unity_player.h"
|
#include "util/unity_player.h"
|
||||||
#include "util/execexe.h"
|
#include "util/execexe.h"
|
||||||
#include "acioemu/handle.h"
|
#include "acioemu/handle.h"
|
||||||
#include "misc/wintouchemu.h"
|
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
#include "util/socd_cleaner.h"
|
#include "util/socd_cleaner.h"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "acio/icca/icca.h"
|
#include "acio/icca/icca.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
#include "misc/wintouchemu.h"
|
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
|
|||||||
@@ -16,16 +16,16 @@
|
|||||||
#include "launcher/launcher.h"
|
#include "launcher/launcher.h"
|
||||||
#include "launcher/logger.h"
|
#include "launcher/logger.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
|
#include "misc/wintouchemu.h"
|
||||||
#include "util/sysutils.h"
|
#include "util/sysutils.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
#include "util/deferlog.h"
|
#include "util/deferlog.h"
|
||||||
#include "misc/nativetouchhook.h"
|
#include "touch/native/nativetouchhook.h"
|
||||||
#include "misc/wintouchemu.h"
|
|
||||||
|
|
||||||
namespace games::popn {
|
namespace games::popn {
|
||||||
|
|
||||||
bool SHOW_PIKA_MONITOR_WARNING = false;
|
bool SHOW_PIKA_MONITOR_WARNING = false;
|
||||||
bool NATIVE_TOUCH = false;
|
bool NATIVE_TOUCH = true;
|
||||||
|
|
||||||
#if SPICE64 && !SPICE_XP
|
#if SPICE64 && !SPICE_XP
|
||||||
|
|
||||||
@@ -715,13 +715,16 @@ namespace games::popn {
|
|||||||
// 00000100 0000000B 00000001 (button 9)
|
// 00000100 0000000B 00000001 (button 9)
|
||||||
// set third column to 0 and it will work with BIO2
|
// set third column to 0 and it will work with BIO2
|
||||||
|
|
||||||
if (!GRAPHICS_WINDOWED) {
|
NATIVE_TOUCH = !wintouchemu::FORCE &&
|
||||||
if (NATIVE_TOUCH) {
|
nativetouch::hook(avs::game::DLL_INSTANCE);
|
||||||
nativetouchhook::hook(avs::game::DLL_INSTANCE);
|
if (!NATIVE_TOUCH) {
|
||||||
} else {
|
wintouchemu::FORCE = true;
|
||||||
wintouchemu::FORCE = true;
|
if (!GRAPHICS_WINDOWED || GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||||
wintouchemu::hook_title_ends("", "Main Screen", avs::game::DLL_INSTANCE);
|
wintouchemu::hook_title_ends(
|
||||||
|
"",
|
||||||
|
"Main Screen",
|
||||||
|
avs::game::DLL_INSTANCE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,9 @@ bool games::rb::ReflecBeatTouchDeviceHandle::open(LPCWSTR lpFileName) {
|
|||||||
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER);
|
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// request automatic aspect ratio fixes
|
||||||
|
::rawinput::touch::ASPECT_COMPENSATION_GAME = true;
|
||||||
|
|
||||||
// create touch window
|
// create touch window
|
||||||
touch_create_wnd(wnd);
|
touch_create_wnd(wnd);
|
||||||
|
|
||||||
@@ -94,9 +97,6 @@ bool games::rb::ReflecBeatTouchDeviceHandle::open(LPCWSTR lpFileName) {
|
|||||||
ShowWindow(wnd, SW_SHOW);
|
ShowWindow(wnd, SW_SHOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
// request automatic aspect ratio fixes
|
|
||||||
::rawinput::touch::ASPECT_COMPENSATION_GAME = true;
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// fallback to dx hook
|
// fallback to dx hook
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
#include "hooks/devicehook.h"
|
#include "hooks/devicehook.h"
|
||||||
#include "hooks/libraryhook.h"
|
#include "hooks/libraryhook.h"
|
||||||
|
#ifdef SPICE64
|
||||||
|
#include "hooks/graphics/nvapi_impl.h"
|
||||||
|
#endif
|
||||||
#include "hooks/graphics/nvapi_hook.h"
|
#include "hooks/graphics/nvapi_hook.h"
|
||||||
#include "hooks/powrprof.h"
|
#include "hooks/powrprof.h"
|
||||||
#include "hooks/sleephook.h"
|
#include "hooks/sleephook.h"
|
||||||
@@ -23,8 +26,8 @@
|
|||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
#include "util/sysutils.h"
|
#include "util/sysutils.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "misc/nativetouchhook.h"
|
|
||||||
#include "misc/wintouchemu.h"
|
#include "misc/wintouchemu.h"
|
||||||
|
#include "touch/native/nativetouchhook.h"
|
||||||
#include "bi2x_hook.h"
|
#include "bi2x_hook.h"
|
||||||
#include "camera.h"
|
#include "camera.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
@@ -46,7 +49,6 @@ namespace games::sdvx {
|
|||||||
const char *ORIGINAL_ASIO_DEVICE_NAME = "XONAR SOUND CARD(64)";
|
const char *ORIGINAL_ASIO_DEVICE_NAME = "XONAR SOUND CARD(64)";
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
bool NATIVETOUCH = false;
|
|
||||||
uint8_t DIGITAL_KNOB_SENS = 16;
|
uint8_t DIGITAL_KNOB_SENS = 16;
|
||||||
SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM;
|
SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM;
|
||||||
bool ENABLE_COM_PORT_SCAN_HOOK = false;
|
bool ENABLE_COM_PORT_SCAN_HOOK = false;
|
||||||
@@ -428,28 +430,41 @@ namespace games::sdvx {
|
|||||||
if (aio != nullptr) {
|
if (aio != nullptr) {
|
||||||
|
|
||||||
// enable 9on12 for AMD
|
// enable 9on12 for AMD
|
||||||
if (!libutils::try_library("nvapi64.dll")) {
|
const auto nvapi = libutils::try_library("nvapi64.dll");
|
||||||
|
if (nvapi == nullptr) {
|
||||||
log_info(
|
log_info(
|
||||||
"sdvx",
|
"sdvx",
|
||||||
"nvapi64.dll not found; for non-NVIDIA GPUs, requesting 9on12 to be enabled");
|
"nvapi64.dll not found; for non-NVIDIA GPUs, requesting 9on12 to be enabled");
|
||||||
GRAPHICS_9_ON_12_REQUESTED_BY_GAME = true;
|
GRAPHICS_9_ON_12_REQUESTED_BY_GAME = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nvapi_hook::BYPASS_NVAPI || nvapi == nullptr) {
|
||||||
|
const uint32_t main_refresh_hz = GRAPHICS_FORCE_REFRESH > 0 ?
|
||||||
|
GRAPHICS_FORCE_REFRESH : (is_valkyrie_model() ? 120 : 60);
|
||||||
|
const uint32_t sub_refresh_hz = GRAPHICS_FORCE_REFRESH_SUB.value_or(60);
|
||||||
|
if (!nvapi_impl::initialize(
|
||||||
|
avs::game::DLL_INSTANCE,
|
||||||
|
main_refresh_hz,
|
||||||
|
sub_refresh_hz)) {
|
||||||
|
log_warning("sdvx", "failed to initialize synthetic NVAPI");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// don't let nvapi mess with display settings
|
// don't let nvapi mess with display settings
|
||||||
nvapi_hook::initialize(avs::game::DLL_INSTANCE);
|
nvapi_hook::initialize(avs::game::DLL_INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_valkyrie_model()) {
|
if (is_valkyrie_model()) {
|
||||||
if (NATIVETOUCH) {
|
const auto native_touch_ready = !wintouchemu::FORCE &&
|
||||||
nativetouchhook::hook(avs::game::DLL_INSTANCE);
|
nativetouch::hook(avs::game::DLL_INSTANCE);
|
||||||
} else if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
|
if (!native_touch_ready) {
|
||||||
// hook touch window
|
|
||||||
// in windowed mode, game can accept mouse input on the second screen
|
|
||||||
wintouchemu::FORCE = true;
|
wintouchemu::FORCE = true;
|
||||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
if (!GRAPHICS_WINDOWED) {
|
||||||
wintouchemu::hook_title_ends(
|
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||||
|
wintouchemu::hook_title_ends(
|
||||||
"SOUND VOLTEX",
|
"SOUND VOLTEX",
|
||||||
"Main Screen",
|
"Main Screen",
|
||||||
avs::game::DLL_INSTANCE);
|
avs::game::DLL_INSTANCE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert BI2X hooks
|
// insert BI2X hooks
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ namespace games::sdvx {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
extern bool NATIVETOUCH;
|
|
||||||
extern uint8_t DIGITAL_KNOB_SENS;
|
extern uint8_t DIGITAL_KNOB_SENS;
|
||||||
extern std::optional<std::string> ASIO_DRIVER;
|
extern std::optional<std::string> ASIO_DRIVER;
|
||||||
extern SdvxOverlayPosition OVERLAY_POS;
|
extern SdvxOverlayPosition OVERLAY_POS;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <audioclient.h>
|
#include <audioclient.h>
|
||||||
#include <mmdeviceapi.h>
|
#include <mmdeviceapi.h>
|
||||||
|
|
||||||
|
#include "avs/game.h"
|
||||||
#include "hooks/audio/backends/mmdevice/device_enumerator.h"
|
#include "hooks/audio/backends/mmdevice/device_enumerator.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
#include "audio_private.h"
|
#include "audio_private.h"
|
||||||
#include "acm.h"
|
#include "acm.h"
|
||||||
#include "asio_proxy.h"
|
#include "asio_proxy.h"
|
||||||
|
#include "xact.h"
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
DEFINE_GUID(CLSID_MMDeviceEnumerator,
|
DEFINE_GUID(CLSID_MMDeviceEnumerator,
|
||||||
@@ -53,9 +55,39 @@ namespace hooks::audio {
|
|||||||
std::string ASIO_DRIVER_NAME = "";
|
std::string ASIO_DRIVER_NAME = "";
|
||||||
bool ASIO_FORCE_UNLOAD_ON_STOP = false;
|
bool ASIO_FORCE_UNLOAD_ON_STOP = false;
|
||||||
|
|
||||||
// private globals
|
|
||||||
IAudioClient *CLIENT = nullptr;
|
|
||||||
std::mutex INITIALIZE_LOCK; // for asio
|
std::mutex INITIALIZE_LOCK; // for asio
|
||||||
|
|
||||||
|
IAudioClient *CLIENT = nullptr;
|
||||||
|
static std::mutex CLIENT_LOCK;
|
||||||
|
static bool CLIENT_STOPPING = false;
|
||||||
|
|
||||||
|
void set_active_client(IAudioClient *client, const char *source) {
|
||||||
|
IAudioClient *previous_client = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard lock(CLIENT_LOCK);
|
||||||
|
|
||||||
|
if (CLIENT_STOPPING || CLIENT == client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (client) {
|
||||||
|
client->AddRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
previous_client = CLIENT;
|
||||||
|
CLIENT = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous_client) {
|
||||||
|
previous_client->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client) {
|
||||||
|
log_info(
|
||||||
|
"audio",
|
||||||
|
"active client selected by {} after successful initialization",
|
||||||
|
source);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static HRESULT STDAPICALLTYPE CoCreateInstance_hook(
|
static HRESULT STDAPICALLTYPE CoCreateInstance_hook(
|
||||||
@@ -129,15 +161,24 @@ namespace hooks::audio {
|
|||||||
|
|
||||||
// general hooks
|
// general hooks
|
||||||
CoCreateInstance_orig = detour::iat_try("CoCreateInstance", CoCreateInstance_hook);
|
CoCreateInstance_orig = detour::iat_try("CoCreateInstance", CoCreateInstance_hook);
|
||||||
|
if (avs::game::is_model("PAN")) {
|
||||||
|
hooks::audio::xact::init();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
log_info("audio", "stopping");
|
log_info("audio", "stopping");
|
||||||
if (CLIENT) {
|
IAudioClient *client = nullptr;
|
||||||
CLIENT->Stop();
|
{
|
||||||
CLIENT->Release();
|
std::lock_guard lock(CLIENT_LOCK);
|
||||||
|
CLIENT_STOPPING = true;
|
||||||
|
client = CLIENT;
|
||||||
CLIENT = nullptr;
|
CLIENT = nullptr;
|
||||||
}
|
}
|
||||||
|
if (client) {
|
||||||
|
client->Stop();
|
||||||
|
client->Release();
|
||||||
|
}
|
||||||
stop_low_latency();
|
stop_low_latency();
|
||||||
|
|
||||||
// release the ASIO drivers we kept pinned for the process lifetime now that we are
|
// release the ASIO drivers we kept pinned for the process lifetime now that we are
|
||||||
|
|||||||
@@ -14,4 +14,6 @@ namespace hooks::audio {
|
|||||||
extern std::mutex INITIALIZE_LOCK;
|
extern std::mutex INITIALIZE_LOCK;
|
||||||
extern bool VOLUME_HOOK_ENABLED;
|
extern bool VOLUME_HOOK_ENABLED;
|
||||||
extern LowLatencyAudioClient *LOW_LATENCY_CLIENT;
|
extern LowLatencyAudioClient *LOW_LATENCY_CLIENT;
|
||||||
|
|
||||||
|
void set_active_client(IAudioClient *client, const char *source);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,11 +95,6 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
|||||||
}
|
}
|
||||||
std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock);
|
std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock);
|
||||||
|
|
||||||
// release old audio client if initialized
|
|
||||||
if (hooks::audio::CLIENT) {
|
|
||||||
hooks::audio::CLIENT->Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
IAudioClient *client = nullptr;
|
IAudioClient *client = nullptr;
|
||||||
if (iid == IID_IAudioClient) {
|
if (iid == IID_IAudioClient) {
|
||||||
client = wrap_audio_client(reinterpret_cast<IAudioClient *>(*ppInterface));
|
client = wrap_audio_client(reinterpret_cast<IAudioClient *>(*ppInterface));
|
||||||
@@ -107,9 +102,6 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
|||||||
client = wrap_audio_client3(reinterpret_cast<IAudioClient3 *>(*ppInterface));
|
client = wrap_audio_client3(reinterpret_cast<IAudioClient3 *>(*ppInterface));
|
||||||
}
|
}
|
||||||
*ppInterface = client;
|
*ppInterface = client;
|
||||||
// persist the audio client
|
|
||||||
hooks::audio::CLIENT = client;
|
|
||||||
hooks::audio::CLIENT->AddRef();
|
|
||||||
|
|
||||||
} else if (iid == __uuidof(IAudioEndpointVolume) && hooks::audio::VOLUME_HOOK_ENABLED) {
|
} else if (iid == __uuidof(IAudioEndpointVolume) && hooks::audio::VOLUME_HOOK_ENABLED) {
|
||||||
*ppInterface = new WrappedIAudioEndpointVolume(reinterpret_cast<IAudioEndpointVolume *>(*ppInterface));
|
*ppInterface = new WrappedIAudioEndpointVolume(reinterpret_cast<IAudioEndpointVolume *>(*ppInterface));
|
||||||
|
|||||||
@@ -153,19 +153,9 @@ HRESULT STDMETHODCALLTYPE NullMMDevice::Activate(
|
|||||||
log_info("audio::null", "NullMMDevice::Activate {}", guid2s(iid));
|
log_info("audio::null", "NullMMDevice::Activate {}", guid2s(iid));
|
||||||
|
|
||||||
if (iid == IID_IAudioClient) {
|
if (iid == IID_IAudioClient) {
|
||||||
|
|
||||||
// release any previously persisted client
|
|
||||||
if (hooks::audio::CLIENT != nullptr) {
|
|
||||||
hooks::audio::CLIENT->Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *client = static_cast<IAudioClient *>(new DummyIAudioClient(new NullDiscardBackend()));
|
auto *client = static_cast<IAudioClient *>(new DummyIAudioClient(new NullDiscardBackend()));
|
||||||
*ppInterface = client;
|
*ppInterface = client;
|
||||||
|
|
||||||
// persist the audio client
|
|
||||||
hooks::audio::CLIENT = client;
|
|
||||||
hooks::audio::CLIENT->AddRef();
|
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "hooks/audio/util.h"
|
#include "hooks/audio/util.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/precise_timer.h"
|
|
||||||
|
|
||||||
NullDiscardBackend::~NullDiscardBackend() {
|
NullDiscardBackend::~NullDiscardBackend() {
|
||||||
this->running = false;
|
this->running = false;
|
||||||
@@ -125,8 +125,6 @@ HRESULT NullDiscardBackend::on_release_buffer(uint32_t, DWORD) {
|
|||||||
void NullDiscardBackend::pace_loop() {
|
void NullDiscardBackend::pace_loop() {
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
timeutils::PreciseSleepTimer timer;
|
|
||||||
|
|
||||||
// audio is discarded, so timing precision and drift do not matter; just wake the
|
// audio is discarded, so timing precision and drift do not matter; just wake the
|
||||||
// game once per buffer period to keep its render thread from blocking on the event.
|
// game once per buffer period to keep its render thread from blocking on the event.
|
||||||
const auto period = duration_cast<steady_clock::duration>(
|
const auto period = duration_cast<steady_clock::duration>(
|
||||||
@@ -136,6 +134,6 @@ void NullDiscardBackend::pace_loop() {
|
|||||||
if (this->relay_handle) {
|
if (this->relay_handle) {
|
||||||
SetEvent(this->relay_handle);
|
SetEvent(this->relay_handle);
|
||||||
}
|
}
|
||||||
timer.sleep(period);
|
std::this_thread::sleep_for(period);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,6 +302,7 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize(
|
|||||||
device_format->nChannels * (device_format->wBitsPerSample / 8));
|
device_format->nChannels * (device_format->wBitsPerSample / 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hooks::audio::set_active_client(this, "WrappedIAudioClient::Initialize");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetBufferSize(UINT32 *pNumBufferFrames) {
|
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetBufferSize(UINT32 *pNumBufferFrames) {
|
||||||
@@ -651,5 +652,6 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::InitializeSharedAudioStream(
|
|||||||
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream success, hr={}", FMT_HRESULT(ret));
|
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream success, hr={}", FMT_HRESULT(ret));
|
||||||
copy_wave_format(&hooks::audio::FORMAT, pFormat);
|
copy_wave_format(&hooks::audio::FORMAT, pFormat);
|
||||||
copy_wave_format(&this->device_format, pFormat);
|
copy_wave_format(&this->device_format, pFormat);
|
||||||
|
hooks::audio::set_active_client(this, "WrappedIAudioClient::InitializeSharedAudioStream");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,13 +75,17 @@ HRESULT STDMETHODCALLTYPE DummyIAudioClient::Initialize(
|
|||||||
log_info("audio::wasapi", "IAudioClient::Initialize hook hit");
|
log_info("audio::wasapi", "IAudioClient::Initialize hook hit");
|
||||||
print_format(ShareMode, StreamFlags, hnsBufferDuration, hnsPeriodicity, pFormat);
|
print_format(ShareMode, StreamFlags, hnsBufferDuration, hnsPeriodicity, pFormat);
|
||||||
|
|
||||||
CHECK_RESULT(this->backend->on_initialize(
|
HRESULT ret = this->backend->on_initialize(
|
||||||
&ShareMode,
|
&ShareMode,
|
||||||
&StreamFlags,
|
&StreamFlags,
|
||||||
&hnsBufferDuration,
|
&hnsBufferDuration,
|
||||||
&hnsPeriodicity,
|
&hnsPeriodicity,
|
||||||
pFormat,
|
pFormat,
|
||||||
AudioSessionGuid));
|
AudioSessionGuid);
|
||||||
|
if (SUCCEEDED(ret)) {
|
||||||
|
hooks::audio::set_active_client(this, "DummyIAudioClient::Initialize");
|
||||||
|
}
|
||||||
|
CHECK_RESULT(ret);
|
||||||
}
|
}
|
||||||
HRESULT STDMETHODCALLTYPE DummyIAudioClient::GetBufferSize(UINT32 *pNumBufferFrames) {
|
HRESULT STDMETHODCALLTYPE DummyIAudioClient::GetBufferSize(UINT32 *pNumBufferFrames) {
|
||||||
static std::once_flag printed;
|
static std::once_flag printed;
|
||||||
|
|||||||
@@ -0,0 +1,393 @@
|
|||||||
|
#include "xact.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <initguid.h>
|
||||||
|
#include <mmreg.h>
|
||||||
|
#include <objbase.h>
|
||||||
|
|
||||||
|
#include "util/deferlog.h"
|
||||||
|
#include "util/detour.h"
|
||||||
|
#include "util/logging.h"
|
||||||
|
#include "util/utils.h"
|
||||||
|
|
||||||
|
namespace hooks::audio::xact {
|
||||||
|
|
||||||
|
// XAudio 2.7 is a COM API. Newer Windows SDKs expose a different IXAudio2
|
||||||
|
// layout, so keep this proxy pinned to the legacy ABI used by libxact.
|
||||||
|
struct XAudio2DeviceDetails {
|
||||||
|
WCHAR device_id[256];
|
||||||
|
WCHAR display_name[256];
|
||||||
|
DWORD role;
|
||||||
|
WAVEFORMATEXTENSIBLE output_format;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct XAudio2EffectChain {
|
||||||
|
UINT32 effect_count;
|
||||||
|
const void *effect_descriptors;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IXAudio2_27 {
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **object) = 0;
|
||||||
|
virtual ULONG STDMETHODCALLTYPE AddRef() = 0;
|
||||||
|
virtual ULONG STDMETHODCALLTYPE Release() = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE GetDeviceCount(UINT32 *device_count) = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE GetDeviceDetails(
|
||||||
|
UINT32 device_index,
|
||||||
|
XAudio2DeviceDetails *device_details) = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE Initialize(UINT32 flags, UINT32 processor) = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE RegisterForCallbacks(void *callback) = 0;
|
||||||
|
virtual void STDMETHODCALLTYPE UnregisterForCallbacks(void *callback) = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE CreateSourceVoice(
|
||||||
|
void **source_voice,
|
||||||
|
const WAVEFORMATEX *source_format,
|
||||||
|
UINT32 flags,
|
||||||
|
float max_frequency_ratio,
|
||||||
|
void *callback,
|
||||||
|
const void *send_list,
|
||||||
|
const XAudio2EffectChain *effect_chain) = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE CreateSubmixVoice(
|
||||||
|
void **submix_voice,
|
||||||
|
UINT32 input_channels,
|
||||||
|
UINT32 input_sample_rate,
|
||||||
|
UINT32 flags,
|
||||||
|
UINT32 processing_stage,
|
||||||
|
const void *send_list,
|
||||||
|
const XAudio2EffectChain *effect_chain) = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE CreateMasteringVoice(
|
||||||
|
void **mastering_voice,
|
||||||
|
UINT32 input_channels,
|
||||||
|
UINT32 input_sample_rate,
|
||||||
|
UINT32 flags,
|
||||||
|
UINT32 device_index,
|
||||||
|
const XAudio2EffectChain *effect_chain) = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE StartEngine() = 0;
|
||||||
|
virtual void STDMETHODCALLTYPE StopEngine() = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE CommitChanges(UINT32 operation_set) = 0;
|
||||||
|
virtual void STDMETHODCALLTYPE GetPerformanceData(void *performance_data) = 0;
|
||||||
|
virtual void STDMETHODCALLTYPE SetDebugConfiguration(
|
||||||
|
const void *debug_configuration,
|
||||||
|
void *reserved) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// XAudio2 2.7 COM class and interface.
|
||||||
|
DEFINE_GUID(CLSID_XAudio2_7_LEGACY,
|
||||||
|
0x5a508685, 0xa254, 0x4fba,
|
||||||
|
0x9b, 0x82, 0x9a, 0x24, 0xb0, 0x03, 0x06, 0xaf);
|
||||||
|
DEFINE_GUID(IID_IXAudio2_7_LEGACY,
|
||||||
|
0x8bcf1f58, 0x9fe7, 0x4583,
|
||||||
|
0x8a, 0xc6, 0xe2, 0xad, 0xc4, 0x65, 0xc8, 0xbb);
|
||||||
|
|
||||||
|
static decltype(CoCreateInstance) *CoCreateInstance_orig = nullptr;
|
||||||
|
|
||||||
|
using CreateFX_t = HRESULT (WINAPI *)(REFCLSID, IUnknown **, const void *, UINT32);
|
||||||
|
static CreateFX_t CreateFX_orig = nullptr;
|
||||||
|
|
||||||
|
static std::string describe_wave_format(const WAVEFORMATEX *format) {
|
||||||
|
if (format == nullptr) {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD channel_mask = 0;
|
||||||
|
if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
|
||||||
|
format->cbSize >= sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) {
|
||||||
|
channel_mask = reinterpret_cast<const WAVEFORMATEXTENSIBLE *>(format)->dwChannelMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt::format(
|
||||||
|
"tag=0x{:04x}, channels={}, rate={} Hz, bits={}, valid_block={} B, avg={} B/s, mask=0x{:08x}",
|
||||||
|
format->wFormatTag,
|
||||||
|
format->nChannels,
|
||||||
|
format->nSamplesPerSec,
|
||||||
|
format->wBitsPerSample,
|
||||||
|
format->nBlockAlign,
|
||||||
|
format->nAvgBytesPerSec,
|
||||||
|
channel_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t Size>
|
||||||
|
static std::string narrow_fixed(const WCHAR (&value)[Size]) {
|
||||||
|
size_t length = 0;
|
||||||
|
while (length < Size && value[length] != L'\0') {
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
return ws2s(std::wstring(value, length));
|
||||||
|
}
|
||||||
|
|
||||||
|
class WrappedXAudio2 final : public IXAudio2_27 {
|
||||||
|
public:
|
||||||
|
explicit WrappedXAudio2(IXAudio2_27 *real) : real(real) {
|
||||||
|
log_info("audio::xaudio2", "wrapping IXAudio2 2.7 engine {}", static_cast<void *>(real));
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **object) override {
|
||||||
|
if (object == nullptr) {
|
||||||
|
return E_POINTER;
|
||||||
|
}
|
||||||
|
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IXAudio2_7_LEGACY)) {
|
||||||
|
*object = this;
|
||||||
|
AddRef();
|
||||||
|
log_info("audio::xaudio2", "IXAudio2::QueryInterface({}) -> proxy", guid2s(riid));
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto result = real->QueryInterface(riid, object);
|
||||||
|
log_info(
|
||||||
|
"audio::xaudio2",
|
||||||
|
"IXAudio2::QueryInterface({}) -> {}, object={}",
|
||||||
|
guid2s(riid),
|
||||||
|
FMT_HRESULT(result),
|
||||||
|
object != nullptr ? *object : nullptr);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ULONG STDMETHODCALLTYPE AddRef() override {
|
||||||
|
return ++ref_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
ULONG STDMETHODCALLTYPE Release() override {
|
||||||
|
const auto remaining = --ref_count;
|
||||||
|
if (remaining == 0) {
|
||||||
|
log_info("audio::xaudio2", "destroying IXAudio2 2.7 proxy");
|
||||||
|
real->Release();
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
return remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT STDMETHODCALLTYPE GetDeviceCount(UINT32 *device_count) override {
|
||||||
|
const auto result = real->GetDeviceCount(device_count);
|
||||||
|
log_info(
|
||||||
|
"audio::xaudio2",
|
||||||
|
"IXAudio2::GetDeviceCount -> {}, count={}",
|
||||||
|
FMT_HRESULT(result),
|
||||||
|
SUCCEEDED(result) && device_count != nullptr ? *device_count : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT STDMETHODCALLTYPE GetDeviceDetails(
|
||||||
|
UINT32 device_index,
|
||||||
|
XAudio2DeviceDetails *device_details) override {
|
||||||
|
const auto result = real->GetDeviceDetails(device_index, device_details);
|
||||||
|
if (SUCCEEDED(result) && device_details != nullptr) {
|
||||||
|
const auto device_name = narrow_fixed(device_details->display_name);
|
||||||
|
if (!device_details_logged.exchange(true, std::memory_order_relaxed)) {
|
||||||
|
log_info(
|
||||||
|
"audio::xaudio2",
|
||||||
|
"IXAudio2::GetDeviceDetails({}) -> {}, id='{}', name='{}', role=0x{:08x}, {}",
|
||||||
|
device_index,
|
||||||
|
FMT_HRESULT(result),
|
||||||
|
narrow_fixed(device_details->device_id),
|
||||||
|
device_name,
|
||||||
|
device_details->role,
|
||||||
|
describe_wave_format(&device_details->output_format.Format));
|
||||||
|
}
|
||||||
|
const auto channels = device_details->output_format.Format.nChannels;
|
||||||
|
if (channels != 2 && channels != 6 &&
|
||||||
|
!channel_warning_logged.exchange(true, std::memory_order_relaxed)) {
|
||||||
|
log_warning(
|
||||||
|
"audio::xaudio2",
|
||||||
|
"output device '{}' has {} channels; Nostalgia requires stereo or 5.1 output",
|
||||||
|
device_name,
|
||||||
|
channels);
|
||||||
|
deferredlogs::defer_error_messages({
|
||||||
|
"unsupported audio output channel count detected!",
|
||||||
|
fmt::format(" device: {}", device_name),
|
||||||
|
fmt::format(" detected {} channels; Nostalgia requires 2 (stereo) or 6 (5.1)", channels),
|
||||||
|
" * configure the default Windows playback device for stereo or 5.1 output",
|
||||||
|
" * disable 7.1 surround sound or spatial audio for this device",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log_warning(
|
||||||
|
"audio::xaudio2",
|
||||||
|
"IXAudio2::GetDeviceDetails({}) -> {}",
|
||||||
|
device_index,
|
||||||
|
FMT_HRESULT(result));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT STDMETHODCALLTYPE Initialize(UINT32 flags, UINT32 processor) override {
|
||||||
|
const auto result = real->Initialize(flags, processor);
|
||||||
|
log_info(
|
||||||
|
"audio::xaudio2",
|
||||||
|
"IXAudio2::Initialize(flags=0x{:08x}, processor=0x{:08x}) -> {}",
|
||||||
|
flags,
|
||||||
|
processor,
|
||||||
|
FMT_HRESULT(result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT STDMETHODCALLTYPE RegisterForCallbacks(void *callback) override {
|
||||||
|
const auto result = real->RegisterForCallbacks(callback);
|
||||||
|
log_info(
|
||||||
|
"audio::xaudio2",
|
||||||
|
"IXAudio2::RegisterForCallbacks({}) -> {}",
|
||||||
|
callback,
|
||||||
|
FMT_HRESULT(result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void STDMETHODCALLTYPE UnregisterForCallbacks(void *callback) override {
|
||||||
|
log_info("audio::xaudio2", "IXAudio2::UnregisterForCallbacks({})", callback);
|
||||||
|
real->UnregisterForCallbacks(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT STDMETHODCALLTYPE CreateSourceVoice(
|
||||||
|
void **source_voice,
|
||||||
|
const WAVEFORMATEX *source_format,
|
||||||
|
UINT32 flags,
|
||||||
|
float max_frequency_ratio,
|
||||||
|
void *callback,
|
||||||
|
const void *send_list,
|
||||||
|
const XAudio2EffectChain *effect_chain) override {
|
||||||
|
return real->CreateSourceVoice(
|
||||||
|
source_voice,
|
||||||
|
source_format,
|
||||||
|
flags,
|
||||||
|
max_frequency_ratio,
|
||||||
|
callback,
|
||||||
|
send_list,
|
||||||
|
effect_chain);
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT STDMETHODCALLTYPE CreateSubmixVoice(
|
||||||
|
void **submix_voice,
|
||||||
|
UINT32 input_channels,
|
||||||
|
UINT32 input_sample_rate,
|
||||||
|
UINT32 flags,
|
||||||
|
UINT32 processing_stage,
|
||||||
|
const void *send_list,
|
||||||
|
const XAudio2EffectChain *effect_chain) override {
|
||||||
|
return real->CreateSubmixVoice(
|
||||||
|
submix_voice,
|
||||||
|
input_channels,
|
||||||
|
input_sample_rate,
|
||||||
|
flags,
|
||||||
|
processing_stage,
|
||||||
|
send_list,
|
||||||
|
effect_chain);
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT STDMETHODCALLTYPE CreateMasteringVoice(
|
||||||
|
void **mastering_voice,
|
||||||
|
UINT32 input_channels,
|
||||||
|
UINT32 input_sample_rate,
|
||||||
|
UINT32 flags,
|
||||||
|
UINT32 device_index,
|
||||||
|
const XAudio2EffectChain *effect_chain) override {
|
||||||
|
const auto result = real->CreateMasteringVoice(
|
||||||
|
mastering_voice,
|
||||||
|
input_channels,
|
||||||
|
input_sample_rate,
|
||||||
|
flags,
|
||||||
|
device_index,
|
||||||
|
effect_chain);
|
||||||
|
log_info(
|
||||||
|
"audio::xaudio2",
|
||||||
|
"IXAudio2::CreateMasteringVoice(channels={}, rate={} Hz, flags=0x{:08x}, device={}, effects={}) -> {}, voice={}",
|
||||||
|
input_channels,
|
||||||
|
input_sample_rate,
|
||||||
|
flags,
|
||||||
|
device_index,
|
||||||
|
effect_chain != nullptr ? effect_chain->effect_count : 0,
|
||||||
|
FMT_HRESULT(result),
|
||||||
|
mastering_voice != nullptr ? *mastering_voice : nullptr);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT STDMETHODCALLTYPE StartEngine() override {
|
||||||
|
const auto result = real->StartEngine();
|
||||||
|
log_info("audio::xaudio2", "IXAudio2::StartEngine -> {}", FMT_HRESULT(result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void STDMETHODCALLTYPE StopEngine() override {
|
||||||
|
log_info("audio::xaudio2", "IXAudio2::StopEngine");
|
||||||
|
real->StopEngine();
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT STDMETHODCALLTYPE CommitChanges(UINT32 operation_set) override {
|
||||||
|
return real->CommitChanges(operation_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
void STDMETHODCALLTYPE GetPerformanceData(void *performance_data) override {
|
||||||
|
real->GetPerformanceData(performance_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void STDMETHODCALLTYPE SetDebugConfiguration(
|
||||||
|
const void *debug_configuration,
|
||||||
|
void *reserved) override {
|
||||||
|
log_info("audio::xaudio2", "IXAudio2::SetDebugConfiguration({})", debug_configuration);
|
||||||
|
real->SetDebugConfiguration(debug_configuration, reserved);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic<ULONG> ref_count = 1;
|
||||||
|
std::atomic_bool device_details_logged = false;
|
||||||
|
std::atomic_bool channel_warning_logged = false;
|
||||||
|
IXAudio2_27 *real;
|
||||||
|
};
|
||||||
|
|
||||||
|
static HRESULT STDAPICALLTYPE CoCreateInstance_hook(
|
||||||
|
REFCLSID clsid,
|
||||||
|
LPUNKNOWN outer,
|
||||||
|
DWORD class_context,
|
||||||
|
REFIID iid,
|
||||||
|
LPVOID *object) {
|
||||||
|
const auto result = CoCreateInstance_orig(clsid, outer, class_context, iid, object);
|
||||||
|
log_info(
|
||||||
|
"audio::xact",
|
||||||
|
"CoCreateInstance(clsid={}, iid={}, context=0x{:08x}) -> {}, object={}",
|
||||||
|
guid2s(clsid),
|
||||||
|
guid2s(iid),
|
||||||
|
class_context,
|
||||||
|
FMT_HRESULT(result),
|
||||||
|
object != nullptr ? *object : nullptr);
|
||||||
|
|
||||||
|
if (SUCCEEDED(result) && object != nullptr && *object != nullptr &&
|
||||||
|
IsEqualCLSID(clsid, CLSID_XAudio2_7_LEGACY) &&
|
||||||
|
IsEqualIID(iid, IID_IXAudio2_7_LEGACY)) {
|
||||||
|
*object = static_cast<IXAudio2_27 *>(
|
||||||
|
new WrappedXAudio2(static_cast<IXAudio2_27 *>(*object)));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HRESULT WINAPI CreateFX_hook(
|
||||||
|
REFCLSID clsid,
|
||||||
|
IUnknown **effect,
|
||||||
|
const void *init_data,
|
||||||
|
UINT32 init_data_size) {
|
||||||
|
const auto result = CreateFX_orig(clsid, effect, init_data, init_data_size);
|
||||||
|
log_info(
|
||||||
|
"audio::xapofx",
|
||||||
|
"CreateFX(clsid={}, init_data={}, size={}) -> {}, effect={}",
|
||||||
|
guid2s(clsid),
|
||||||
|
init_data,
|
||||||
|
init_data_size,
|
||||||
|
FMT_HRESULT(result),
|
||||||
|
effect != nullptr ? static_cast<void *>(*effect) : nullptr);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
const auto libxact = GetModuleHandleW(L"libxact.dll");
|
||||||
|
if (libxact == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoCreateInstance_orig = detour::iat_try(
|
||||||
|
"CoCreateInstance", CoCreateInstance_hook, libxact);
|
||||||
|
CreateFX_orig = detour::iat_try("CreateFX", CreateFX_hook, libxact);
|
||||||
|
|
||||||
|
log_info(
|
||||||
|
"audio::xact",
|
||||||
|
"libxact hooks installed: CoCreateInstance={}, CreateFX={}",
|
||||||
|
CoCreateInstance_orig != nullptr,
|
||||||
|
CreateFX_orig != nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace hooks::audio::xact {
|
||||||
|
void init();
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "util/fileutils.h"
|
#include "util/fileutils.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "misc/wintouchemu.h"
|
#include "misc/wintouchemu.h"
|
||||||
|
#include "touch/native/inject.h"
|
||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
|
|
||||||
@@ -188,11 +189,13 @@ static bool gitadora_should_block_game_window_placement(HWND hWnd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto window_name = gitadora_window_name_for_hwnd(hWnd);
|
const auto window_name = gitadora_window_name_for_hwnd(hWnd);
|
||||||
return window_name != nullptr && graphics_gitadora_has_window_monitor(window_name);
|
return window_name != nullptr && graphics_gitadora_has_window_override(window_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gitadora_remember_window(HWND hWnd, const std::string &window_name) {
|
static void gitadora_remember_window(HWND hWnd, const std::string &window_name) {
|
||||||
if (window_name == "LEFT") {
|
if (window_name == "GITADORA") {
|
||||||
|
GRAPHICS_HOOKED_WINDOW = hWnd;
|
||||||
|
} else if (window_name == "LEFT") {
|
||||||
GFDM_LEFT_WINDOW = hWnd;
|
GFDM_LEFT_WINDOW = hWnd;
|
||||||
} else if (window_name == "RIGHT") {
|
} else if (window_name == "RIGHT") {
|
||||||
GFDM_RIGHT_WINDOW = hWnd;
|
GFDM_RIGHT_WINDOW = hWnd;
|
||||||
@@ -589,6 +592,12 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
|||||||
hWndParent, hMenu, hInstance, lpParam);
|
hWndParent, hMenu, hInstance, lpParam);
|
||||||
GRAPHICS_WINDOWS.push_back(result);
|
GRAPHICS_WINDOWS.push_back(result);
|
||||||
|
|
||||||
|
// remember these windows now because full capture happens later during D3D
|
||||||
|
// initialization; placement calls before then can undo creation-time overrides
|
||||||
|
if (result != nullptr && is_gfdm_window && !is_gfdm_sub_window) {
|
||||||
|
gitadora_remember_window(result, gfdm_window_name);
|
||||||
|
}
|
||||||
|
|
||||||
// theme the native title bar (dark/light)
|
// theme the native title bar (dark/light)
|
||||||
set_window_dark_titlebar(result);
|
set_window_dark_titlebar(result);
|
||||||
|
|
||||||
@@ -609,9 +618,6 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
|||||||
}
|
}
|
||||||
|
|
||||||
// only hook touch window if multiple windows are allowed
|
// only hook touch window if multiple windows are allowed
|
||||||
if (gfdm_window_name == "LEFT" || gfdm_window_name == "RIGHT") {
|
|
||||||
gitadora_remember_window(result, gfdm_window_name);
|
|
||||||
}
|
|
||||||
if (is_gfdm_sub_window && GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
if (is_gfdm_sub_window && GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||||
gitadora_remember_window(result, gfdm_window_name);
|
gitadora_remember_window(result, gfdm_window_name);
|
||||||
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
|
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
|
||||||
@@ -797,7 +803,11 @@ static BOOL WINAPI MoveWindow_hook(HWND hWnd, int X, int Y, int nWidth, int nHei
|
|||||||
nWidth = rect.right - rect.left;
|
nWidth = rect.right - rect.left;
|
||||||
nHeight = rect.bottom - rect.top;
|
nHeight = rect.bottom - rect.top;
|
||||||
|
|
||||||
touch_attach_wnd(TDJ_SUBSCREEN_WINDOW);
|
if (games::iidx::NATIVE_TOUCH) {
|
||||||
|
nativetouch::inject::register_and_attach_window(TDJ_SUBSCREEN_WINDOW);
|
||||||
|
} else {
|
||||||
|
touch_attach_wnd(TDJ_SUBSCREEN_WINDOW);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Existing behaviour: suppress subscreen window and prompt user to use overlay instead
|
// Existing behaviour: suppress subscreen window and prompt user to use overlay instead
|
||||||
log_info(
|
log_info(
|
||||||
@@ -950,6 +960,13 @@ static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (games::iidx::TDJ_MODE &&
|
||||||
|
GRAPHICS_PREVENT_SECONDARY_WINDOWS &&
|
||||||
|
hWnd == TDJ_SUBSCREEN_WINDOW) {
|
||||||
|
log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// call original
|
// call original
|
||||||
return ShowWindow_orig(hWnd, nCmdShow);
|
return ShowWindow_orig(hWnd, nCmdShow);
|
||||||
}
|
}
|
||||||
@@ -1163,6 +1180,14 @@ void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParamet
|
|||||||
WNDPROC_ORIG = reinterpret_cast<WNDPROC>(GetWindowLongPtrA(hWnd, GWLP_WNDPROC));
|
WNDPROC_ORIG = reinterpret_cast<WNDPROC>(GetWindowLongPtrA(hWnd, GWLP_WNDPROC));
|
||||||
SetWindowLongPtrA(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WindowProc));
|
SetWindowLongPtrA(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WindowProc));
|
||||||
|
|
||||||
|
const bool native_touch_overlay =
|
||||||
|
(games::iidx::NATIVE_TOUCH && games::iidx::TDJ_MODE && !GRAPHICS_IIDX_WSUB) ||
|
||||||
|
(games::popn::NATIVE_TOUCH &&
|
||||||
|
games::popn::is_pikapika_model() && GRAPHICS_PREVENT_SECONDARY_WINDOWS);
|
||||||
|
if (native_touch_overlay) {
|
||||||
|
nativetouch::inject::register_and_attach_window(hWnd);
|
||||||
|
}
|
||||||
|
|
||||||
// NOLEGACY causes WM_CHAR to be not received
|
// NOLEGACY causes WM_CHAR to be not received
|
||||||
// reflec beat game engine does not pass WM_CHAR through for some reason (unrelated to SpiceTouch)
|
// reflec beat game engine does not pass WM_CHAR through for some reason (unrelated to SpiceTouch)
|
||||||
if (!rawinput::NOLEGACY && !(avs::game::is_model({"KBR", "LBR", "MBR"}))) {
|
if (!rawinput::NOLEGACY && !(avs::game::is_model({"KBR", "LBR", "MBR"}))) {
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ extern std::string GRAPHICS_GITADORA_MAIN_MONITOR;
|
|||||||
extern std::string GRAPHICS_GITADORA_LEFT_MONITOR;
|
extern std::string GRAPHICS_GITADORA_LEFT_MONITOR;
|
||||||
extern std::string GRAPHICS_GITADORA_RIGHT_MONITOR;
|
extern std::string GRAPHICS_GITADORA_RIGHT_MONITOR;
|
||||||
extern std::string GRAPHICS_GITADORA_SMALL_MONITOR;
|
extern std::string GRAPHICS_GITADORA_SMALL_MONITOR;
|
||||||
|
extern std::optional<std::string> GRAPHICS_GITADORA_SMALL_SIZE;
|
||||||
|
extern std::optional<std::string> GRAPHICS_GITADORA_SMALL_POS;
|
||||||
|
|
||||||
extern bool GRAPHICS_IIDX_WSUB;
|
extern bool GRAPHICS_IIDX_WSUB;
|
||||||
extern std::optional<std::string> GRAPHICS_WSUB_SIZE;
|
extern std::optional<std::string> GRAPHICS_WSUB_SIZE;
|
||||||
@@ -157,7 +159,7 @@ bool graphics_gitadora_apply_window_monitor(
|
|||||||
int &width,
|
int &width,
|
||||||
int &height,
|
int &height,
|
||||||
bool log_change);
|
bool log_change);
|
||||||
bool graphics_gitadora_has_window_monitor(const std::string &window_name);
|
bool graphics_gitadora_has_window_override(const std::string &window_name);
|
||||||
|
|
||||||
void change_primary_monitor(const std::string &monitor_name);
|
void change_primary_monitor(const std::string &monitor_name);
|
||||||
void update_monitor_on_boot(
|
void update_monitor_on_boot(
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ std::string GRAPHICS_GITADORA_MAIN_MONITOR;
|
|||||||
std::string GRAPHICS_GITADORA_LEFT_MONITOR;
|
std::string GRAPHICS_GITADORA_LEFT_MONITOR;
|
||||||
std::string GRAPHICS_GITADORA_RIGHT_MONITOR;
|
std::string GRAPHICS_GITADORA_RIGHT_MONITOR;
|
||||||
std::string GRAPHICS_GITADORA_SMALL_MONITOR;
|
std::string GRAPHICS_GITADORA_SMALL_MONITOR;
|
||||||
|
std::optional<std::string> GRAPHICS_GITADORA_SMALL_SIZE;
|
||||||
|
std::optional<std::string> GRAPHICS_GITADORA_SMALL_POS;
|
||||||
|
|
||||||
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
|
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
|
||||||
bool GRAPHICS_IIDX_WSUB = false;
|
bool GRAPHICS_IIDX_WSUB = false;
|
||||||
@@ -89,9 +91,12 @@ static const std::string &graphics_gitadora_monitor_for_window_name(
|
|||||||
return empty;
|
return empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool graphics_gitadora_has_window_monitor(const std::string &window_name) {
|
bool graphics_gitadora_has_window_override(const std::string &window_name) {
|
||||||
const auto &monitor_name = graphics_gitadora_monitor_for_window_name(window_name);
|
const auto &monitor_name = graphics_gitadora_monitor_for_window_name(window_name);
|
||||||
return !monitor_name.empty();
|
return !monitor_name.empty() ||
|
||||||
|
(window_name == "SMALL" &&
|
||||||
|
(GRAPHICS_GITADORA_SMALL_SIZE.has_value() ||
|
||||||
|
GRAPHICS_GITADORA_SMALL_POS.has_value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool graphics_monitor_rect_from_name(
|
static bool graphics_monitor_rect_from_name(
|
||||||
@@ -212,7 +217,11 @@ bool graphics_gitadora_apply_window_monitor(
|
|||||||
int &width,
|
int &width,
|
||||||
int &height,
|
int &height,
|
||||||
bool log_change) {
|
bool log_change) {
|
||||||
return graphics_gitadora_apply_monitor_rect(
|
if (!GRAPHICS_WINDOWED || !games::gitadora::is_arena_model()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool applied = graphics_gitadora_apply_monitor_rect(
|
||||||
graphics_gitadora_monitor_for_window_name(window_name),
|
graphics_gitadora_monitor_for_window_name(window_name),
|
||||||
window_name,
|
window_name,
|
||||||
x,
|
x,
|
||||||
@@ -220,6 +229,42 @@ bool graphics_gitadora_apply_window_monitor(
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
log_change);
|
log_change);
|
||||||
|
|
||||||
|
if (window_name != "SMALL") {
|
||||||
|
return applied;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<uint32_t, uint32_t> result;
|
||||||
|
if (GRAPHICS_GITADORA_SMALL_SIZE.has_value()) {
|
||||||
|
if (parse_width_height(GRAPHICS_GITADORA_SMALL_SIZE.value(), result)) {
|
||||||
|
width = result.first;
|
||||||
|
height = result.second;
|
||||||
|
applied = true;
|
||||||
|
} else {
|
||||||
|
log_fatal("graphics-windowed", "failed to parse -gdwsmallsize");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (GRAPHICS_GITADORA_SMALL_POS.has_value()) {
|
||||||
|
if (parse_width_height(GRAPHICS_GITADORA_SMALL_POS.value(), result)) {
|
||||||
|
x = result.first;
|
||||||
|
y = result.second;
|
||||||
|
applied = true;
|
||||||
|
} else {
|
||||||
|
log_fatal("graphics-windowed", "failed to parse -gdwsmallpos");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applied && log_change &&
|
||||||
|
(GRAPHICS_GITADORA_SMALL_SIZE.has_value() || GRAPHICS_GITADORA_SMALL_POS.has_value())) {
|
||||||
|
log_info(
|
||||||
|
"graphics-windowed",
|
||||||
|
"GITADORA SMALL custom override: pos=({}, {}), size={}x{}",
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height);
|
||||||
|
}
|
||||||
|
return applied;
|
||||||
}
|
}
|
||||||
|
|
||||||
void graphics_capture_initial_window(HWND hWnd) {
|
void graphics_capture_initial_window(HWND hWnd) {
|
||||||
@@ -254,7 +299,7 @@ void graphics_capture_initial_window(HWND hWnd) {
|
|||||||
cfg::SCREENRESIZE->init_client_width = client_w;
|
cfg::SCREENRESIZE->init_client_width = client_w;
|
||||||
cfg::SCREENRESIZE->init_client_height = client_h;
|
cfg::SCREENRESIZE->init_client_height = client_h;
|
||||||
cfg::SCREENRESIZE->init_client_aspect_ratio = (float)client_w / (float)client_h;
|
cfg::SCREENRESIZE->init_client_aspect_ratio = (float)client_w / (float)client_h;
|
||||||
log_debug(
|
log_misc(
|
||||||
"graphics-windowed",
|
"graphics-windowed",
|
||||||
"[{}] graphics_capture_initial_window initial window size {}x{}, ratio {}",
|
"[{}] graphics_capture_initial_window initial window size {}x{}, ratio {}",
|
||||||
fmt::ptr(hWnd),
|
fmt::ptr(hWnd),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "external/nvapi/nvapi.h"
|
#include "external/nvapi/nvapi.h"
|
||||||
#include "external/nvapi/NvApiDriverSettings.h"
|
#include "external/nvapi/NvApiDriverSettings.h"
|
||||||
|
#include "avs/game.h"
|
||||||
#include "hooks/libraryhook.h"
|
#include "hooks/libraryhook.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
@@ -12,14 +13,21 @@ namespace nvapi_hook {
|
|||||||
bool BYPASS_NVAPI = false;
|
bool BYPASS_NVAPI = false;
|
||||||
|
|
||||||
typedef uintptr_t *(*NvAPI_QueryInterface_t)(unsigned int);
|
typedef uintptr_t *(*NvAPI_QueryInterface_t)(unsigned int);
|
||||||
|
|
||||||
static NvAPI_QueryInterface_t NvAPI_QueryInterface_orig = nullptr;
|
static NvAPI_QueryInterface_t NvAPI_QueryInterface_orig = nullptr;
|
||||||
|
static decltype(NvAPI_GPU_GetConnectedDisplayIds) *NvAPI_GPU_GetConnectedDisplayIds_orig = nullptr;
|
||||||
|
static decltype(NvAPI_DISP_GetGDIPrimaryDisplayId) *NvAPI_DISP_GetGDIPrimaryDisplayId_orig = nullptr;
|
||||||
|
|
||||||
static uintptr_t* __cdecl NvAPI_QueryInterface_hook(unsigned int func_code);
|
static uintptr_t* __cdecl NvAPI_QueryInterface_hook(unsigned int func_code);
|
||||||
|
static NvAPI_Status __cdecl NvAPI_GPU_GetConnectedDisplayIds_hook(
|
||||||
|
NvPhysicalGpuHandle hPhysicalGpu,
|
||||||
|
NV_GPU_DISPLAYIDS *pDisplayIds,
|
||||||
|
NvU32 *pDisplayIdCount,
|
||||||
|
NvU32 flags);
|
||||||
static NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_hook(
|
static NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_hook(
|
||||||
NvU32 pathInfoCount, NV_DISPLAYCONFIG_PATH_INFO *pathInfo, NvU32 flags);
|
NvU32 pathInfoCount, NV_DISPLAYCONFIG_PATH_INFO *pathInfo, NvU32 flags);
|
||||||
|
|
||||||
void initialize(HINSTANCE dll) {
|
void initialize(HINSTANCE dll) {
|
||||||
|
|
||||||
#ifdef SPICE64
|
#ifdef SPICE64
|
||||||
std::string nvapi_dll = "nvapi64.dll";
|
std::string nvapi_dll = "nvapi64.dll";
|
||||||
#else
|
#else
|
||||||
@@ -40,6 +48,18 @@ namespace nvapi_hook {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// spoof display connector types only for SDVX
|
||||||
|
if (func_code == 0x0078DBA2 && avs::game::is_model("KFC")) {
|
||||||
|
NvAPI_GPU_GetConnectedDisplayIds_orig =
|
||||||
|
reinterpret_cast<decltype(NvAPI_GPU_GetConnectedDisplayIds) *>(
|
||||||
|
NvAPI_QueryInterface_orig(func_code));
|
||||||
|
NvAPI_DISP_GetGDIPrimaryDisplayId_orig =
|
||||||
|
reinterpret_cast<decltype(NvAPI_DISP_GetGDIPrimaryDisplayId) *>(
|
||||||
|
NvAPI_QueryInterface_orig(0x1E9D8A31));
|
||||||
|
log_misc("nvapi_hook", "NvAPI_QueryInterface(NvAPI_GPU_GetConnectedDisplayIds) - hooked");
|
||||||
|
return (uintptr_t *)NvAPI_GPU_GetConnectedDisplayIds_hook;
|
||||||
|
}
|
||||||
|
|
||||||
// NvAPI_DISP_SetDisplayConfig
|
// NvAPI_DISP_SetDisplayConfig
|
||||||
if (func_code == 0x5D8CF8DE) {
|
if (func_code == 0x5D8CF8DE) {
|
||||||
log_misc("nvapi_hook", "NvAPI_QueryInterface(NvAPI_DISP_SetDisplayConfig) - hooked");
|
log_misc("nvapi_hook", "NvAPI_QueryInterface(NvAPI_DISP_SetDisplayConfig) - hooked");
|
||||||
@@ -51,8 +71,53 @@ namespace nvapi_hook {
|
|||||||
return NvAPI_QueryInterface_orig(func_code);
|
return NvAPI_QueryInterface_orig(func_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NvAPI_Status __cdecl NvAPI_GPU_GetConnectedDisplayIds_hook(
|
||||||
|
NvPhysicalGpuHandle hPhysicalGpu,
|
||||||
|
NV_GPU_DISPLAYIDS *pDisplayIds,
|
||||||
|
NvU32 *pDisplayIdCount,
|
||||||
|
NvU32 flags) {
|
||||||
|
|
||||||
|
if (NvAPI_GPU_GetConnectedDisplayIds_orig == nullptr) {
|
||||||
|
return NVAPI_NO_IMPLEMENTATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NvAPI_Status status = NvAPI_GPU_GetConnectedDisplayIds_orig(
|
||||||
|
hPhysicalGpu,
|
||||||
|
pDisplayIds,
|
||||||
|
pDisplayIdCount,
|
||||||
|
flags);
|
||||||
|
|
||||||
|
if (status != NVAPI_OK || pDisplayIds == nullptr || pDisplayIdCount == nullptr ||
|
||||||
|
NvAPI_DISP_GetGDIPrimaryDisplayId_orig == nullptr) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
NvU32 primary_display_id = 0;
|
||||||
|
if (NvAPI_DISP_GetGDIPrimaryDisplayId_orig(&primary_display_id) != NVAPI_OK) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// report the primary display adapter as DP and all other displays as HDMI
|
||||||
|
// (the game expects this since the VM cab is like that)
|
||||||
|
//
|
||||||
|
// this doesn't actually affect gameplay; only how
|
||||||
|
// NvDisplayConfig: MainDisplay={}hz / SubDisplay={}hz
|
||||||
|
// gets printed.
|
||||||
|
//
|
||||||
|
// for the actual value that matters for gameplay, it's this:
|
||||||
|
// NvDisplayConfig: GetMainDisplayRefreshRate = {}hz
|
||||||
|
for (NvU32 index = 0; index < *pDisplayIdCount; index++) {
|
||||||
|
auto &display = pDisplayIds[index];
|
||||||
|
display.connectorType = display.displayId == primary_display_id ?
|
||||||
|
NV_MONITOR_CONN_TYPE_DP : NV_MONITOR_CONN_TYPE_HDMI;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_hook(
|
NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_hook(
|
||||||
NvU32 pathInfoCount, NV_DISPLAYCONFIG_PATH_INFO *pathInfo, NvU32 flags) {
|
NvU32 pathInfoCount, NV_DISPLAYCONFIG_PATH_INFO *pathInfo, NvU32 flags) {
|
||||||
|
// do not let the game apply its emulated cabinet topology to Windows
|
||||||
log_misc("nvapi_hook", "NvAPI_DISP_SetDisplayConfig_hook - do nothing and return");
|
log_misc("nvapi_hook", "NvAPI_DISP_SetDisplayConfig_hook - do nothing and return");
|
||||||
return NVAPI_OK;
|
return NVAPI_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,480 @@
|
|||||||
|
#include "nvapi_impl.h"
|
||||||
|
|
||||||
|
#ifdef SPICE64
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "external/nvapi/nvapi.h"
|
||||||
|
#include "hooks/libraryhook.h"
|
||||||
|
#include "util/logging.h"
|
||||||
|
#include "util/sysutils.h"
|
||||||
|
|
||||||
|
namespace nvapi_impl {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr unsigned int NVAPI_INITIALIZE_ID = 0x0150E828;
|
||||||
|
constexpr unsigned int NVAPI_INITIALIZE_EX_ID = 0xAD298D3F;
|
||||||
|
constexpr unsigned int NVAPI_UNLOAD_ID = 0xD22BDD7E;
|
||||||
|
constexpr unsigned int NVAPI_ENUM_PHYSICAL_GPUS_ID = 0xE5AC921F;
|
||||||
|
constexpr unsigned int NVAPI_GPU_GET_CONNECTED_DISPLAY_IDS_ID = 0x0078DBA2;
|
||||||
|
constexpr unsigned int NVAPI_DISP_GET_GDI_PRIMARY_DISPLAY_ID = 0x1E9D8A31;
|
||||||
|
constexpr unsigned int NVAPI_DISP_GET_DISPLAY_CONFIG_ID = 0x11ABCCF8;
|
||||||
|
constexpr unsigned int NVAPI_DISP_SET_DISPLAY_CONFIG_ID = 0x5D8CF8DE;
|
||||||
|
|
||||||
|
constexpr char NVAPI_DLL_NAME_A[] = "nvapi64.dll";
|
||||||
|
|
||||||
|
struct SyntheticDisplay {
|
||||||
|
NvU32 display_id;
|
||||||
|
NvU32 width;
|
||||||
|
NvU32 height;
|
||||||
|
NvU32 color_depth;
|
||||||
|
NvS32 x;
|
||||||
|
NvS32 y;
|
||||||
|
NvU32 refresh_rate_1k;
|
||||||
|
NV_ROTATE rotation;
|
||||||
|
bool primary;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool provider_initialized = false;
|
||||||
|
static bool nvapi_initialized = false;
|
||||||
|
static int gpu_handle_storage = 0;
|
||||||
|
// snapshot of the Win32 display state exposed through synthetic NVAPI
|
||||||
|
static std::vector<SyntheticDisplay> displays;
|
||||||
|
|
||||||
|
static NvPhysicalGpuHandle get_gpu_handle() {
|
||||||
|
return reinterpret_cast<NvPhysicalGpuHandle>(&gpu_handle_storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
static NV_ROTATE get_rotation(DWORD orientation) {
|
||||||
|
switch (orientation) {
|
||||||
|
case DMDO_90:
|
||||||
|
return NV_ROTATE_90;
|
||||||
|
case DMDO_180:
|
||||||
|
return NV_ROTATE_180;
|
||||||
|
case DMDO_270:
|
||||||
|
return NV_ROTATE_270;
|
||||||
|
default:
|
||||||
|
return NV_ROTATE_0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<SyntheticDisplay> enumerate_displays(
|
||||||
|
uint32_t main_refresh_hz,
|
||||||
|
uint32_t sub_refresh_hz) {
|
||||||
|
|
||||||
|
std::vector<SyntheticDisplay> result;
|
||||||
|
|
||||||
|
// reuse the active monitor list, then read live modes after -mainmonitor changes
|
||||||
|
for (const auto &monitor : sysutils::enumerate_monitors()) {
|
||||||
|
DEVMODEA mode {};
|
||||||
|
mode.dmSize = sizeof(mode);
|
||||||
|
if (!EnumDisplaySettingsExA(
|
||||||
|
monitor.display_name.c_str(),
|
||||||
|
ENUM_CURRENT_SETTINGS,
|
||||||
|
&mode,
|
||||||
|
0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool primary = mode.dmPosition.x == 0 && mode.dmPosition.y == 0;
|
||||||
|
result.push_back({
|
||||||
|
.display_id = 0,
|
||||||
|
.width = mode.dmPelsWidth,
|
||||||
|
.height = mode.dmPelsHeight,
|
||||||
|
.color_depth = mode.dmBitsPerPel > 0 ? mode.dmBitsPerPel : 32,
|
||||||
|
.x = mode.dmPosition.x,
|
||||||
|
.y = mode.dmPosition.y,
|
||||||
|
.refresh_rate_1k = 0,
|
||||||
|
.rotation = get_rotation(mode.dmDisplayOrientation),
|
||||||
|
.primary = primary,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stable_sort(result.begin(), result.end(), [](const auto &left, const auto &right) {
|
||||||
|
return left.primary && !right.primary;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.size() > 2) {
|
||||||
|
result.resize(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.empty()) {
|
||||||
|
result.push_back({
|
||||||
|
.display_id = 0,
|
||||||
|
.width = 1920,
|
||||||
|
.height = 1080,
|
||||||
|
.color_depth = 32,
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.refresh_rate_1k = 0,
|
||||||
|
.rotation = NV_ROTATE_0,
|
||||||
|
.primary = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t index = 0; index < result.size(); index++) {
|
||||||
|
auto &display = result[index];
|
||||||
|
display.primary = index == 0;
|
||||||
|
display.display_id = 0x80000000u | static_cast<NvU32>(index + 1);
|
||||||
|
const uint32_t refresh_hz = index == 0 ? main_refresh_hz : sub_refresh_hz;
|
||||||
|
display.refresh_rate_1k = refresh_hz * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializes NVAPI for the calling process.
|
||||||
|
// marks the synthetic provider initialized without contacting a driver.
|
||||||
|
static NvAPI_Status __cdecl NvAPI_Initialize_impl() {
|
||||||
|
log_misc("nvapi_impl", "NvAPI_Initialize");
|
||||||
|
nvapi_initialized = true;
|
||||||
|
return NVAPI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializes NVAPI with additional client flags.
|
||||||
|
// accepts the flags and marks the synthetic provider initialized.
|
||||||
|
static NvAPI_Status __cdecl NvAPI_InitializeEx_impl(NvU32 flags) {
|
||||||
|
log_misc("nvapi_impl", "NvAPI_InitializeEx(flags={:#x})", flags);
|
||||||
|
nvapi_initialized = true;
|
||||||
|
return NVAPI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// releases NVAPI state held for the calling process.
|
||||||
|
// clears the synthetic initialization state while leaving the provider installed.
|
||||||
|
static NvAPI_Status __cdecl NvAPI_Unload_impl() {
|
||||||
|
log_misc("nvapi_impl", "NvAPI_Unload");
|
||||||
|
nvapi_initialized = false;
|
||||||
|
return NVAPI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// enumerates physical GPU handles managed by the NVIDIA driver.
|
||||||
|
// returns one stable synthetic GPU containing all exposed displays.
|
||||||
|
static NvAPI_Status __cdecl NvAPI_EnumPhysicalGPUs_impl(
|
||||||
|
NvPhysicalGpuHandle gpu_handles[NVAPI_MAX_PHYSICAL_GPUS],
|
||||||
|
NvU32 *gpu_count) {
|
||||||
|
|
||||||
|
log_misc(
|
||||||
|
"nvapi_impl",
|
||||||
|
"NvAPI_EnumPhysicalGPUs(handles={}, count={})",
|
||||||
|
fmt::ptr(gpu_handles),
|
||||||
|
fmt::ptr(gpu_count));
|
||||||
|
|
||||||
|
if (!nvapi_initialized) {
|
||||||
|
return NVAPI_API_NOT_INITIALIZED;
|
||||||
|
}
|
||||||
|
if (gpu_handles == nullptr || gpu_count == nullptr) {
|
||||||
|
return NVAPI_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpu_handles[0] = get_gpu_handle();
|
||||||
|
*gpu_count = 1;
|
||||||
|
log_misc(
|
||||||
|
"nvapi_impl",
|
||||||
|
"NvAPI_EnumPhysicalGPUs - gpu={}, count={}",
|
||||||
|
fmt::ptr(gpu_handles[0]),
|
||||||
|
*gpu_count);
|
||||||
|
return NVAPI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns connected display descriptors for a physical GPU.
|
||||||
|
// exposes the monitor snapshot as DP primary and HDMI secondary displays.
|
||||||
|
static NvAPI_Status __cdecl NvAPI_GPU_GetConnectedDisplayIds_impl(
|
||||||
|
NvPhysicalGpuHandle gpu_handle,
|
||||||
|
NV_GPU_DISPLAYIDS *display_ids,
|
||||||
|
NvU32 *display_id_count,
|
||||||
|
NvU32 flags) {
|
||||||
|
|
||||||
|
const NvU32 input_count = display_id_count != nullptr ? *display_id_count : 0;
|
||||||
|
log_misc(
|
||||||
|
"nvapi_impl",
|
||||||
|
"NvAPI_GPU_GetConnectedDisplayIds(gpu={}, ids={}, count={}, flags={:#x})",
|
||||||
|
fmt::ptr(gpu_handle),
|
||||||
|
fmt::ptr(display_ids),
|
||||||
|
input_count,
|
||||||
|
flags);
|
||||||
|
|
||||||
|
if (!nvapi_initialized) {
|
||||||
|
return NVAPI_API_NOT_INITIALIZED;
|
||||||
|
}
|
||||||
|
if (gpu_handle != get_gpu_handle()) {
|
||||||
|
return NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE;
|
||||||
|
}
|
||||||
|
if (display_id_count == nullptr) {
|
||||||
|
return NVAPI_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NvU32 required_count = static_cast<NvU32>(displays.size());
|
||||||
|
if (display_ids == nullptr) {
|
||||||
|
*display_id_count = required_count;
|
||||||
|
log_misc(
|
||||||
|
"nvapi_impl",
|
||||||
|
"NvAPI_GPU_GetConnectedDisplayIds - required_count={}",
|
||||||
|
required_count);
|
||||||
|
return NVAPI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NvU32 capacity = *display_id_count;
|
||||||
|
*display_id_count = required_count;
|
||||||
|
if (capacity < required_count) {
|
||||||
|
return NVAPI_INSUFFICIENT_BUFFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (NvU32 index = 0; index < required_count; index++) {
|
||||||
|
const auto &source = displays[index];
|
||||||
|
auto &destination = display_ids[index];
|
||||||
|
destination = {};
|
||||||
|
destination.version = NV_GPU_DISPLAYIDS_VER;
|
||||||
|
destination.connectorType = source.primary ?
|
||||||
|
NV_MONITOR_CONN_TYPE_DP : NV_MONITOR_CONN_TYPE_HDMI;
|
||||||
|
destination.displayId = source.display_id;
|
||||||
|
destination.isActive = 1;
|
||||||
|
destination.isOSVisible = 1;
|
||||||
|
destination.isConnected = 1;
|
||||||
|
destination.isPhysicallyConnected = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_misc(
|
||||||
|
"nvapi_impl",
|
||||||
|
"NvAPI_GPU_GetConnectedDisplayIds - returned_count={}",
|
||||||
|
required_count);
|
||||||
|
return NVAPI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the NVAPI display ID associated with the Windows GDI primary.
|
||||||
|
// returns the first synthetic display, ordered from the live desktop origin.
|
||||||
|
static NvAPI_Status __cdecl NvAPI_DISP_GetGDIPrimaryDisplayId_impl(NvU32 *display_id) {
|
||||||
|
log_misc(
|
||||||
|
"nvapi_impl",
|
||||||
|
"NvAPI_DISP_GetGDIPrimaryDisplayId(display_id={})",
|
||||||
|
fmt::ptr(display_id));
|
||||||
|
|
||||||
|
if (!nvapi_initialized) {
|
||||||
|
return NVAPI_API_NOT_INITIALIZED;
|
||||||
|
}
|
||||||
|
if (display_id == nullptr || displays.empty()) {
|
||||||
|
return NVAPI_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
*display_id = displays.front().display_id;
|
||||||
|
log_misc(
|
||||||
|
"nvapi_impl",
|
||||||
|
"NvAPI_DISP_GetGDIPrimaryDisplayId - display_id={:#x}",
|
||||||
|
*display_id);
|
||||||
|
return NVAPI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fill_source_mode(
|
||||||
|
NV_DISPLAYCONFIG_SOURCE_MODE_INFO *destination,
|
||||||
|
const SyntheticDisplay &source) {
|
||||||
|
|
||||||
|
if (destination == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*destination = {};
|
||||||
|
destination->resolution.width = source.width;
|
||||||
|
destination->resolution.height = source.height;
|
||||||
|
destination->resolution.colorDepth = source.color_depth;
|
||||||
|
destination->colorFormat = NV_FORMAT_A8R8G8B8;
|
||||||
|
destination->position.x = source.x;
|
||||||
|
destination->position.y = source.y;
|
||||||
|
destination->spanningOrientation = NV_DISPLAYCONFIG_SPAN_NONE;
|
||||||
|
destination->bGDIPrimary = source.primary ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NvAPI_Status fill_target(
|
||||||
|
NV_DISPLAYCONFIG_PATH_TARGET_INFO *destination,
|
||||||
|
const SyntheticDisplay &source,
|
||||||
|
NvU32 target_id) {
|
||||||
|
|
||||||
|
if (destination == nullptr) {
|
||||||
|
return NVAPI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *details = destination->details;
|
||||||
|
destination->displayId = source.display_id;
|
||||||
|
destination->targetId = target_id;
|
||||||
|
|
||||||
|
if (details == nullptr) {
|
||||||
|
return NVAPI_OK;
|
||||||
|
}
|
||||||
|
if (details->version != NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO_VER) {
|
||||||
|
return NVAPI_INCOMPATIBLE_STRUCT_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
*details = {};
|
||||||
|
details->version = NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO_VER;
|
||||||
|
details->rotation = source.rotation;
|
||||||
|
details->scaling = NV_SCALING_DEFAULT;
|
||||||
|
details->refreshRate1K = source.refresh_rate_1k;
|
||||||
|
details->timingOverride = NV_TIMING_OVERRIDE_CURRENT;
|
||||||
|
return NVAPI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieves the current global display topology through NVAPI's three-pass contract.
|
||||||
|
// fills caller-owned buffers from the synthetic monitor snapshot and configured rates.
|
||||||
|
static NvAPI_Status __cdecl NvAPI_DISP_GetDisplayConfig_impl(
|
||||||
|
NvU32 *path_info_count,
|
||||||
|
NV_DISPLAYCONFIG_PATH_INFO *path_info) {
|
||||||
|
|
||||||
|
const NvU32 input_count = path_info_count != nullptr ? *path_info_count : 0;
|
||||||
|
log_misc(
|
||||||
|
"nvapi_impl",
|
||||||
|
"NvAPI_DISP_GetDisplayConfig(count={}, paths={})",
|
||||||
|
input_count,
|
||||||
|
fmt::ptr(path_info));
|
||||||
|
|
||||||
|
if (!nvapi_initialized) {
|
||||||
|
return NVAPI_API_NOT_INITIALIZED;
|
||||||
|
}
|
||||||
|
if (path_info_count == nullptr) {
|
||||||
|
return NVAPI_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NvU32 required_count = static_cast<NvU32>(displays.size());
|
||||||
|
if (path_info == nullptr) {
|
||||||
|
*path_info_count = required_count;
|
||||||
|
log_misc(
|
||||||
|
"nvapi_impl",
|
||||||
|
"NvAPI_DISP_GetDisplayConfig - required_count={}",
|
||||||
|
required_count);
|
||||||
|
return NVAPI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NvU32 capacity = *path_info_count;
|
||||||
|
*path_info_count = required_count;
|
||||||
|
if (capacity < required_count) {
|
||||||
|
return NVAPI_INSUFFICIENT_BUFFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (NvU32 index = 0; index < required_count; index++) {
|
||||||
|
auto &path = path_info[index];
|
||||||
|
if (path.version != NV_DISPLAYCONFIG_PATH_INFO_VER2) {
|
||||||
|
return NVAPI_INCOMPATIBLE_STRUCT_VERSION;
|
||||||
|
}
|
||||||
|
if (path.targetInfo != nullptr && path.targetInfoCount < 1) {
|
||||||
|
return NVAPI_INSUFFICIENT_BUFFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &display = displays[index];
|
||||||
|
path.sourceId = index;
|
||||||
|
path.targetInfoCount = 1;
|
||||||
|
path.IsNonNVIDIAAdapter = 0;
|
||||||
|
path.pOSAdapterID = nullptr;
|
||||||
|
fill_source_mode(path.sourceModeInfo, display);
|
||||||
|
|
||||||
|
const NvAPI_Status status = fill_target(path.targetInfo, display, index);
|
||||||
|
if (status != NVAPI_OK) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_misc(
|
||||||
|
"nvapi_impl",
|
||||||
|
"NvAPI_DISP_GetDisplayConfig - returned_count={}",
|
||||||
|
required_count);
|
||||||
|
return NVAPI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// applies a supplied global display topology through the NVIDIA driver.
|
||||||
|
// accepts the cabinet topology without making any changes to Windows.
|
||||||
|
static NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_impl(
|
||||||
|
NvU32 path_info_count,
|
||||||
|
NV_DISPLAYCONFIG_PATH_INFO *path_info,
|
||||||
|
NvU32 flags) {
|
||||||
|
|
||||||
|
log_misc(
|
||||||
|
"nvapi_impl",
|
||||||
|
"NvAPI_DISP_SetDisplayConfig(count={}, paths={}, flags={:#x})",
|
||||||
|
path_info_count,
|
||||||
|
fmt::ptr(path_info),
|
||||||
|
flags);
|
||||||
|
|
||||||
|
if (!nvapi_initialized) {
|
||||||
|
return NVAPI_API_NOT_INITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_misc("nvapi_impl", "NvAPI_DISP_SetDisplayConfig - return synthetic success");
|
||||||
|
return NVAPI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static uintptr_t *query_result(T function) {
|
||||||
|
return reinterpret_cast<uintptr_t *>(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolves an NVAPI function ID to its implementation address.
|
||||||
|
// exposes only the synthetic entry points used by KFC and rejects all others.
|
||||||
|
static uintptr_t *__cdecl NvAPI_QueryInterface_impl(unsigned int function_id) {
|
||||||
|
uintptr_t *result = nullptr;
|
||||||
|
switch (function_id) {
|
||||||
|
case NVAPI_INITIALIZE_ID:
|
||||||
|
result = query_result(NvAPI_Initialize_impl);
|
||||||
|
break;
|
||||||
|
case NVAPI_INITIALIZE_EX_ID:
|
||||||
|
result = query_result(NvAPI_InitializeEx_impl);
|
||||||
|
break;
|
||||||
|
case NVAPI_UNLOAD_ID:
|
||||||
|
result = query_result(NvAPI_Unload_impl);
|
||||||
|
break;
|
||||||
|
case NVAPI_ENUM_PHYSICAL_GPUS_ID:
|
||||||
|
result = query_result(NvAPI_EnumPhysicalGPUs_impl);
|
||||||
|
break;
|
||||||
|
case NVAPI_GPU_GET_CONNECTED_DISPLAY_IDS_ID:
|
||||||
|
result = query_result(NvAPI_GPU_GetConnectedDisplayIds_impl);
|
||||||
|
break;
|
||||||
|
case NVAPI_DISP_GET_GDI_PRIMARY_DISPLAY_ID:
|
||||||
|
result = query_result(NvAPI_DISP_GetGDIPrimaryDisplayId_impl);
|
||||||
|
break;
|
||||||
|
case NVAPI_DISP_GET_DISPLAY_CONFIG_ID:
|
||||||
|
result = query_result(NvAPI_DISP_GetDisplayConfig_impl);
|
||||||
|
break;
|
||||||
|
case NVAPI_DISP_SET_DISPLAY_CONFIG_ID:
|
||||||
|
result = query_result(NvAPI_DISP_SetDisplayConfig_impl);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_misc(
|
||||||
|
"nvapi_impl",
|
||||||
|
"NvAPI_QueryInterface(0x{:x}) - {}",
|
||||||
|
function_id,
|
||||||
|
result != nullptr ? "implemented" : "unsupported");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool initialize(HINSTANCE dll, uint32_t main_refresh_hz, uint32_t sub_refresh_hz) {
|
||||||
|
if (provider_initialized) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (dll == nullptr) {
|
||||||
|
log_warning("nvapi_impl", "invalid synthetic module handle");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
displays = enumerate_displays(main_refresh_hz, sub_refresh_hz);
|
||||||
|
libraryhook_hook_library(NVAPI_DLL_NAME_A, dll);
|
||||||
|
libraryhook_hook_proc("nvapi_QueryInterface", NvAPI_QueryInterface_impl);
|
||||||
|
libraryhook_enable();
|
||||||
|
|
||||||
|
provider_initialized = true;
|
||||||
|
log_info(
|
||||||
|
"nvapi_impl",
|
||||||
|
"synthetic {} enabled with {} display(s), main={} Hz, sub={} Hz",
|
||||||
|
NVAPI_DLL_NAME_A,
|
||||||
|
displays.size(),
|
||||||
|
main_refresh_hz,
|
||||||
|
sub_refresh_hz);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef SPICE64
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
namespace nvapi_impl {
|
||||||
|
|
||||||
|
bool initialize(HINSTANCE dll, uint32_t main_refresh_hz, uint32_t sub_refresh_hz);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -296,8 +296,9 @@ void hooks::lang::early_init() {
|
|||||||
&GetLocaleInfoA_orig);
|
&GetLocaleInfoA_orig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for TDJ subscreen search keyboard
|
// for TDJ subscreen search keyboard and T44 narrow-string handling
|
||||||
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
|
if ((avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) ||
|
||||||
|
avs::game::is_model("T44")) {
|
||||||
log_info("hooks::lang", "hooking IsDBCSLeadByte");
|
log_info("hooks::lang", "hooking IsDBCSLeadByte");
|
||||||
detour::trampoline_try(
|
detour::trampoline_try(
|
||||||
"kernel32.dll",
|
"kernel32.dll",
|
||||||
@@ -306,7 +307,7 @@ void hooks::lang::early_init() {
|
|||||||
&IsDBCSLeadByte_orig);
|
&IsDBCSLeadByte_orig);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (games::gitadora::is_arena_model()) {
|
if (games::gitadora::is_arena_model() || avs::game::is_model("T44")) {
|
||||||
log_info("hooks::lang", "hooking WideCharToMultiByte");
|
log_info("hooks::lang", "hooking WideCharToMultiByte");
|
||||||
detour::trampoline_try(
|
detour::trampoline_try(
|
||||||
"kernel32.dll",
|
"kernel32.dll",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ static robin_hood::unordered_map<std::string, FARPROC> PROCS;
|
|||||||
|
|
||||||
static decltype(LoadLibraryA) *LoadLibraryA_orig = nullptr;
|
static decltype(LoadLibraryA) *LoadLibraryA_orig = nullptr;
|
||||||
static decltype(LoadLibraryW) *LoadLibraryW_orig = nullptr;
|
static decltype(LoadLibraryW) *LoadLibraryW_orig = nullptr;
|
||||||
|
static decltype(LoadLibraryExW) *LoadLibraryExW_orig = nullptr;
|
||||||
static decltype(GetModuleHandleA) *GetModuleHandleA_orig = nullptr;
|
static decltype(GetModuleHandleA) *GetModuleHandleA_orig = nullptr;
|
||||||
static decltype(GetModuleHandleW) *GetModuleHandleW_orig = nullptr;
|
static decltype(GetModuleHandleW) *GetModuleHandleW_orig = nullptr;
|
||||||
static decltype(GetProcAddress) *GetProcAddress_orig = nullptr;
|
static decltype(GetProcAddress) *GetProcAddress_orig = nullptr;
|
||||||
@@ -44,6 +45,20 @@ static HMODULE WINAPI LoadLibraryW_hook(LPCWSTR lpFileName) {
|
|||||||
return LoadLibraryW_orig(lpFileName);
|
return LoadLibraryW_orig(lpFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static HMODULE WINAPI LoadLibraryExW_hook(LPCWSTR lpFileName, HANDLE hFile, DWORD dwFlags) {
|
||||||
|
|
||||||
|
// check hooks
|
||||||
|
if (lpFileName) {
|
||||||
|
auto module = LIBRARIES_W.find(lpFileName);
|
||||||
|
if (module != LIBRARIES_W.end()) {
|
||||||
|
return module->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback
|
||||||
|
return LoadLibraryExW_orig(lpFileName, hFile, dwFlags);
|
||||||
|
}
|
||||||
|
|
||||||
static HMODULE WINAPI GetModuleHandleA_hook(LPCSTR lpModuleName) {
|
static HMODULE WINAPI GetModuleHandleA_hook(LPCSTR lpModuleName) {
|
||||||
|
|
||||||
// check hooks
|
// check hooks
|
||||||
@@ -103,6 +118,7 @@ void libraryhook_enable(HMODULE module) {
|
|||||||
// detour
|
// detour
|
||||||
detour::trampoline_try("kernel32.dll", "LoadLibraryA", LoadLibraryA_hook, &LoadLibraryA_orig);
|
detour::trampoline_try("kernel32.dll", "LoadLibraryA", LoadLibraryA_hook, &LoadLibraryA_orig);
|
||||||
detour::trampoline_try("kernel32.dll", "LoadLibraryW", LoadLibraryW_hook, &LoadLibraryW_orig);
|
detour::trampoline_try("kernel32.dll", "LoadLibraryW", LoadLibraryW_hook, &LoadLibraryW_orig);
|
||||||
|
detour::trampoline_try("kernel32.dll", "LoadLibraryExW", LoadLibraryExW_hook, &LoadLibraryExW_orig);
|
||||||
detour::trampoline_try("kernel32.dll", "GetModuleHandleA", GetModuleHandleA_hook, &GetModuleHandleA_orig);
|
detour::trampoline_try("kernel32.dll", "GetModuleHandleA", GetModuleHandleA_hook, &GetModuleHandleA_orig);
|
||||||
detour::trampoline_try("kernel32.dll", "GetModuleHandleW", GetModuleHandleW_hook, &GetModuleHandleW_orig);
|
detour::trampoline_try("kernel32.dll", "GetModuleHandleW", GetModuleHandleW_hook, &GetModuleHandleW_orig);
|
||||||
detour::trampoline_try("kernel32.dll", "GetProcAddress", GetProcAddress_hook, &GetProcAddress_orig);
|
detour::trampoline_try("kernel32.dll", "GetProcAddress", GetProcAddress_hook, &GetProcAddress_orig);
|
||||||
|
|||||||
@@ -38,10 +38,10 @@
|
|||||||
#include "games/gitadora/gitadora.h"
|
#include "games/gitadora/gitadora.h"
|
||||||
#include "games/hpm/hpm.h"
|
#include "games/hpm/hpm.h"
|
||||||
#include "games/iidx/iidx.h"
|
#include "games/iidx/iidx.h"
|
||||||
|
#include "games/iidx/poke.h"
|
||||||
#ifdef SPICE64
|
#ifdef SPICE64
|
||||||
#include "games/iidx/camera.h"
|
#include "games/iidx/camera.h"
|
||||||
#endif
|
#endif
|
||||||
#include "games/iidx/poke.h"
|
|
||||||
#include "games/jb/jb.h"
|
#include "games/jb/jb.h"
|
||||||
#include "games/mga/mga.h"
|
#include "games/mga/mga.h"
|
||||||
#include "games/nost/nost.h"
|
#include "games/nost/nost.h"
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
#include "misc/wintouchemu.h"
|
#include "misc/wintouchemu.h"
|
||||||
#include "overlay/overlay.h"
|
#include "overlay/overlay.h"
|
||||||
#include "overlay/notifications.h"
|
#include "overlay/notifications.h"
|
||||||
#include "overlay/windows/patch_manager.h"
|
#include "patcher/patch_manager.h"
|
||||||
#include "overlay/windows/iidx_seg.h"
|
#include "overlay/windows/iidx_seg.h"
|
||||||
#include "overlay/windows/obs.h"
|
#include "overlay/windows/obs.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
@@ -379,6 +379,7 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
if (options[launcher::Options::spice2x_IIDXNoSub].value_bool()) {
|
if (options[launcher::Options::spice2x_IIDXNoSub].value_bool()) {
|
||||||
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
||||||
|
GRAPHICS_PREVENT_SECONDARY_WINDOWS = true;
|
||||||
}
|
}
|
||||||
if (options[launcher::Options::spice2x_SDVXNoSub].value_bool()) {
|
if (options[launcher::Options::spice2x_SDVXNoSub].value_bool()) {
|
||||||
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
||||||
@@ -428,9 +429,6 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
|
|
||||||
if (options[launcher::Options::spice2x_NoD3D9DeviceHook].value_bool()) {
|
if (options[launcher::Options::spice2x_NoD3D9DeviceHook].value_bool()) {
|
||||||
D3D9_DEVICE_HOOK_DISABLE = true;
|
D3D9_DEVICE_HOOK_DISABLE = true;
|
||||||
// touch emulation gets disabled, might as well turn these on
|
|
||||||
games::iidx::NATIVE_TOUCH = true;
|
|
||||||
games::sdvx::NATIVETOUCH = true;
|
|
||||||
// not strictly necessary as it will fail to init anyway, but cleaner to just disable it now
|
// not strictly necessary as it will fail to init anyway, but cleaner to just disable it now
|
||||||
overlay::ENABLED = false;
|
overlay::ENABLED = false;
|
||||||
// leaving these on without dx9hooks result in torn state and therefore failure to draw
|
// leaving these on without dx9hooks result in torn state and therefore failure to draw
|
||||||
@@ -490,9 +488,6 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
if (options[launcher::Options::LoadSoundVoltexModule].value_bool()) {
|
if (options[launcher::Options::LoadSoundVoltexModule].value_bool()) {
|
||||||
attach_sdvx = true;
|
attach_sdvx = true;
|
||||||
}
|
}
|
||||||
if (options[launcher::Options::SDVXNativeTouch].value_bool()) {
|
|
||||||
games::sdvx::NATIVETOUCH = true;
|
|
||||||
}
|
|
||||||
if (options[launcher::Options::spice2x_SDVXDigitalKnobSensitivity].is_active()) {
|
if (options[launcher::Options::spice2x_SDVXDigitalKnobSensitivity].is_active()) {
|
||||||
games::sdvx::DIGITAL_KNOB_SENS = (uint8_t)
|
games::sdvx::DIGITAL_KNOB_SENS = (uint8_t)
|
||||||
options[launcher::Options::spice2x_SDVXDigitalKnobSensitivity].value_uint32();
|
options[launcher::Options::spice2x_SDVXDigitalKnobSensitivity].value_uint32();
|
||||||
@@ -593,9 +588,6 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
if (options[launcher::Options::spice2x_IIDXNoESpec].value_bool()) {
|
if (options[launcher::Options::spice2x_IIDXNoESpec].value_bool()) {
|
||||||
games::iidx::DISABLE_ESPEC_IO = true;
|
games::iidx::DISABLE_ESPEC_IO = true;
|
||||||
}
|
}
|
||||||
if (options[launcher::Options::spice2x_IIDXNativeTouch].value_bool()) {
|
|
||||||
games::iidx::NATIVE_TOUCH = true;
|
|
||||||
}
|
|
||||||
// should come later since this will override a few settings
|
// should come later since this will override a few settings
|
||||||
if (options[launcher::Options::spice2x_IIDXWindowedTDJ].value_bool() ||
|
if (options[launcher::Options::spice2x_IIDXWindowedTDJ].value_bool() ||
|
||||||
(options[launcher::Options::IIDXTDJMode].value_bool() && GRAPHICS_WINDOWED)) {
|
(options[launcher::Options::IIDXTDJMode].value_bool() && GRAPHICS_WINDOWED)) {
|
||||||
@@ -643,9 +635,6 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
if (options[launcher::Options::PopnSubMonitorOverride].is_active()) {
|
if (options[launcher::Options::PopnSubMonitorOverride].is_active()) {
|
||||||
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::PopnSubMonitorOverride].value_text();
|
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::PopnSubMonitorOverride].value_text();
|
||||||
}
|
}
|
||||||
if (options[launcher::Options::PopnNativeTouch].value_bool()) {
|
|
||||||
games::popn::NATIVE_TOUCH = true;
|
|
||||||
}
|
|
||||||
if (options[launcher::Options::PopnSubRedraw].value_bool()) {
|
if (options[launcher::Options::PopnSubRedraw].value_bool()) {
|
||||||
SUBSCREEN_FORCE_REDRAW = true;
|
SUBSCREEN_FORCE_REDRAW = true;
|
||||||
}
|
}
|
||||||
@@ -889,7 +878,7 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
options[launcher::Options::ScreenResizeConfigPath].value_text();
|
options[launcher::Options::ScreenResizeConfigPath].value_text();
|
||||||
}
|
}
|
||||||
if (options[launcher::Options::PatchManagerConfigPath].is_active()) {
|
if (options[launcher::Options::PatchManagerConfigPath].is_active()) {
|
||||||
overlay::windows::PATCH_MANAGER_CFG_PATH_OVERRIDE =
|
patcher::PATCH_MANAGER_CFG_PATH_OVERRIDE =
|
||||||
options[launcher::Options::PatchManagerConfigPath].value_text();
|
options[launcher::Options::PatchManagerConfigPath].value_text();
|
||||||
}
|
}
|
||||||
if (options[launcher::Options::PathToAppConfig].is_active()) {
|
if (options[launcher::Options::PathToAppConfig].is_active()) {
|
||||||
@@ -1286,6 +1275,14 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
GRAPHICS_GITADORA_SMALL_MONITOR =
|
GRAPHICS_GITADORA_SMALL_MONITOR =
|
||||||
options[launcher::Options::GitaDoraWindowedSmallMonitor].value_text();
|
options[launcher::Options::GitaDoraWindowedSmallMonitor].value_text();
|
||||||
}
|
}
|
||||||
|
if (options[launcher::Options::GitaDoraWindowedSmallSize].is_active()) {
|
||||||
|
GRAPHICS_GITADORA_SMALL_SIZE =
|
||||||
|
options[launcher::Options::GitaDoraWindowedSmallSize].value_text();
|
||||||
|
}
|
||||||
|
if (options[launcher::Options::GitaDoraWindowedSmallPosition].is_active()) {
|
||||||
|
GRAPHICS_GITADORA_SMALL_POS =
|
||||||
|
options[launcher::Options::GitaDoraWindowedSmallPosition].value_text();
|
||||||
|
}
|
||||||
|
|
||||||
// IIDX/SDVX Windowed Subscreen
|
// IIDX/SDVX Windowed Subscreen
|
||||||
if (options[launcher::Options::spice2x_IIDXWindowedSubscreenSize].is_active()) {
|
if (options[launcher::Options::spice2x_IIDXWindowedSubscreenSize].is_active()) {
|
||||||
@@ -1786,10 +1783,19 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
|
|
||||||
// enable subscreen touch emulation
|
// enable subscreen touch emulation
|
||||||
if (options[launcher::Options::spice2x_IIDXEmulateSubscreenKeypadTouch].is_active()) {
|
if (options[launcher::Options::spice2x_IIDXEmulateSubscreenKeypadTouch].is_active()) {
|
||||||
games::iidx::poke::enable();
|
games::iidx::ENABLE_POKE = true;
|
||||||
}
|
}
|
||||||
if (options[launcher::Options::NostalgiaPoke].is_active()) {
|
if (options[launcher::Options::NostalgiaPoke].is_active()) {
|
||||||
games::nost::ENABLE_POKE = TRUE;
|
games::nost::ENABLE_POKE = true;
|
||||||
|
}
|
||||||
|
if (options[launcher::Options::NostalgiaTouchMode].is_active()) {
|
||||||
|
if (overlay::ENABLED) {
|
||||||
|
games::nost::ENABLE_TOUCH_MODE = true;
|
||||||
|
} else {
|
||||||
|
log_warning(
|
||||||
|
"launcher",
|
||||||
|
"Nostalgia Touch Mode requires the global overlay; ignoring -nosttouch");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1849,6 +1855,13 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (check_dll("jubeat2019.dll")) {
|
||||||
|
avs::game::DLL_NAME = "jubeat2019.dll";
|
||||||
|
attach_io = true;
|
||||||
|
attach_jb = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// RB
|
// RB
|
||||||
if (check_dll("reflecbeat.dll")) {
|
if (check_dll("reflecbeat.dll")) {
|
||||||
avs::game::DLL_NAME = "reflecbeat.dll";
|
avs::game::DLL_NAME = "reflecbeat.dll";
|
||||||
@@ -2277,7 +2290,7 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
games.push_back(new games::sdvx::SDVXGame());
|
games.push_back(new games::sdvx::SDVXGame());
|
||||||
}
|
}
|
||||||
if (attach_jb) {
|
if (attach_jb) {
|
||||||
avs::core::set_default_heap_size("jubeat.dll");
|
avs::core::set_default_heap_size(avs::game::DLL_NAME);
|
||||||
games.push_back(new games::jb::JBGame());
|
games.push_back(new games::jb::JBGame());
|
||||||
}
|
}
|
||||||
if (attach_nostalgia) {
|
if (attach_nostalgia) {
|
||||||
@@ -2517,10 +2530,8 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
// copy defaults to nvram
|
// copy defaults to nvram
|
||||||
avs::core::copy_defaults();
|
avs::core::copy_defaults();
|
||||||
|
|
||||||
// prepare patches
|
// prepare patches (registers the DLL-load notification before the game DLL loads)
|
||||||
{
|
patcher::init();
|
||||||
overlay::windows::PatchManager patch_manager(nullptr, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load game
|
// load game
|
||||||
avs::game::load_dll();
|
avs::game::load_dll();
|
||||||
@@ -2648,9 +2659,7 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// apply patches
|
// apply patches
|
||||||
{
|
patcher::apply_patches_on_start();
|
||||||
overlay::windows::PatchManager patch_manager(nullptr, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load AVS-EA3
|
// load AVS-EA3
|
||||||
avs::ea3::boot(easrv_port, easrv_maint, easrv_smart);
|
avs::ea3::boot(easrv_port, easrv_maint, easrv_smart);
|
||||||
|
|||||||
@@ -962,13 +962,12 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.category = "Cab Peripherals",
|
.category = "Cab Peripherals",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "SDVX FS Subscreen Native Touch Handling",
|
.title = "SDVX Native Touch (DEPRECATED - no longer needed)",
|
||||||
.name = "sdvxnativetouch",
|
.name = "sdvxnativetouch",
|
||||||
.desc = "Disables touch hooks and lets the game access a touch screen directly. "
|
.desc = "This option does nothing.\n\n"
|
||||||
"Requires a touch screen to be connected as a secondary monitor. "
|
"Native touch handling is now enabled by default and this option is no longer needed.",
|
||||||
"Touch input must be routed to the primary screen via Windows Tablet PC settings. "
|
|
||||||
"Enable this when you get duplicate touch inputs from an actual touch screen.",
|
|
||||||
.type = OptionType::Bool,
|
.type = OptionType::Bool,
|
||||||
|
.hidden = true,
|
||||||
.game_name = "Sound Voltex",
|
.game_name = "Sound Voltex",
|
||||||
.category = "Advanced Game Options",
|
.category = "Advanced Game Options",
|
||||||
},
|
},
|
||||||
@@ -1159,13 +1158,12 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// PopnNativeTouch
|
// PopnNativeTouch
|
||||||
.title = "Pop'n Music PikaPika Native Touch Handling",
|
.title = "Pop'n Music Native Touch (DEPRECATED - no longer needed)",
|
||||||
.name = "popnnativetouch",
|
.name = "popnnativetouch",
|
||||||
.desc = "Disables touch hooks and lets the game access a touch screen directly. "
|
.desc = "This option does nothing.\n\n"
|
||||||
"Requires a touch screen to be connected as a secondary monitor. "
|
"Native touch handling is now enabled by default and this option is no longer needed.",
|
||||||
"Touch input must be routed to the primary screen via Windows Tablet PC settings. "
|
|
||||||
"Enable this when you get duplicate touch inputs from an actual touch screen.",
|
|
||||||
.type = OptionType::Bool,
|
.type = OptionType::Bool,
|
||||||
|
.hidden = true,
|
||||||
.game_name = "Pop'n Music",
|
.game_name = "Pop'n Music",
|
||||||
.category = "Advanced Game Options",
|
.category = "Advanced Game Options",
|
||||||
},
|
},
|
||||||
@@ -1793,10 +1791,10 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.category = "Touch Parameters",
|
.category = "Touch Parameters",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "Force Touch Emulation",
|
.title = "Force Legacy Touch Emulation",
|
||||||
.name = "touchemuforce",
|
.name = "touchemuforce",
|
||||||
.desc = "Force enable hook for GetTouchInputInfo API and inject WM_TOUCH events. "
|
.desc = "Force enable hook for GetTouchInputInfo API and inject WM_TOUCH events. "
|
||||||
"This is automatically enabled when needed and is typically not required. Do not enable this unless you have trouble.",
|
"Do not enable this unless you have trouble with using a mouse to emulate touch input.",
|
||||||
.type = OptionType::Bool,
|
.type = OptionType::Bool,
|
||||||
.category = "Touch Parameters",
|
.category = "Touch Parameters",
|
||||||
},
|
},
|
||||||
@@ -2644,6 +2642,26 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.category = "Game Windowed Settings",
|
.category = "Game Windowed Settings",
|
||||||
.picker = OptionPickerType::Monitor,
|
.picker = OptionPickerType::Monitor,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// GitaDoraWindowedSmallSize
|
||||||
|
.title = "GitaDora Windowed SMALL Size",
|
||||||
|
.name = "gdwsmallsize",
|
||||||
|
.desc = "Size of the GITADORA Arena SMALL touch window. Defaults to (800,1280).",
|
||||||
|
.type = OptionType::Text,
|
||||||
|
.setting_name = "800,1280",
|
||||||
|
.game_name = "GitaDora",
|
||||||
|
.category = "Game Windowed Settings",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// GitaDoraWindowedSmallPosition
|
||||||
|
.title = "GitaDora Windowed SMALL Position",
|
||||||
|
.name = "gdwsmallpos",
|
||||||
|
.desc = "Initial position of the GITADORA Arena SMALL touch window. Defaults to (0,0).",
|
||||||
|
.type = OptionType::Text,
|
||||||
|
.setting_name = "0,0",
|
||||||
|
.game_name = "GitaDora",
|
||||||
|
.category = "Game Windowed Settings",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// spice2x_IIDXWindowedSubscreenSize
|
// spice2x_IIDXWindowedSubscreenSize
|
||||||
.title = "IIDX Windowed Subscreen Size",
|
.title = "IIDX Windowed Subscreen Size",
|
||||||
@@ -2870,15 +2888,14 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// spice2x_IIDXNativeTouch
|
// spice2x_IIDXNativeTouch
|
||||||
.title = "IIDX TDJ Subscreen Native Touch Handling",
|
.title = "IIDX Native Touch (DEPRECATED - no longer needed)",
|
||||||
.name = "sp2x-iidxnativetouch",
|
.name = "sp2x-iidxnativetouch",
|
||||||
.display_name = "iidxnativetouch",
|
.display_name = "iidxnativetouch",
|
||||||
.aliases= "iidxnativetouch",
|
.aliases= "iidxnativetouch",
|
||||||
.desc = "Disables touch hooks and lets the game access a touch screen directly. "
|
.desc = "This option does nothing.\n\n"
|
||||||
"Requires a touch screen to be connected as a secondary monitor. "
|
"Native touch handling is now enabled by default and this option is no longer needed.",
|
||||||
"Touch input must be routed to the primary screen via Windows Tablet PC settings. "
|
|
||||||
"Enable this when you get duplicate touch inputs from an actual touch screen.",
|
|
||||||
.type = OptionType::Bool,
|
.type = OptionType::Bool,
|
||||||
|
.hidden = true,
|
||||||
.game_name = "Beatmania IIDX",
|
.game_name = "Beatmania IIDX",
|
||||||
.category = "Advanced Game Options",
|
.category = "Advanced Game Options",
|
||||||
},
|
},
|
||||||
@@ -3132,6 +3149,21 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.type = OptionType::Bool,
|
.type = OptionType::Bool,
|
||||||
.game_name = "Nostalgia",
|
.game_name = "Nostalgia",
|
||||||
.category = "Game Options",
|
.category = "Game Options",
|
||||||
|
.quick_setting_category = "Game",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// NostalgiaTouchMode
|
||||||
|
.title = "Nostalgia Touch Piano",
|
||||||
|
.name = "nosttouch",
|
||||||
|
.desc =
|
||||||
|
"Allows you to play the piano by touching the screen instead of a controller. "
|
||||||
|
"Use the mode switch button to toggle between interacting with the menu and playing the piano.\n\n"
|
||||||
|
"Velocity sensitivity is not supported. Touch targets will be pixel-perfect aligned with the "
|
||||||
|
"judgement line, key beams, and notes.",
|
||||||
|
.type = OptionType::Bool,
|
||||||
|
.game_name = "Nostalgia",
|
||||||
|
.category = "Game Options",
|
||||||
|
.quick_setting_category = "Game",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// ForceBackBufferCount
|
// ForceBackBufferCount
|
||||||
|
|||||||
@@ -258,6 +258,8 @@ namespace launcher {
|
|||||||
GitaDoraWindowedLeftMonitor,
|
GitaDoraWindowedLeftMonitor,
|
||||||
GitaDoraWindowedRightMonitor,
|
GitaDoraWindowedRightMonitor,
|
||||||
GitaDoraWindowedSmallMonitor,
|
GitaDoraWindowedSmallMonitor,
|
||||||
|
GitaDoraWindowedSmallSize,
|
||||||
|
GitaDoraWindowedSmallPosition,
|
||||||
spice2x_IIDXWindowedSubscreenSize,
|
spice2x_IIDXWindowedSubscreenSize,
|
||||||
spice2x_IIDXWindowedSubscreenPosition,
|
spice2x_IIDXWindowedSubscreenPosition,
|
||||||
IIDXWindowedSubscreenBorderless,
|
IIDXWindowedSubscreenBorderless,
|
||||||
@@ -295,6 +297,7 @@ namespace launcher {
|
|||||||
DDRP4IOBufferMode,
|
DDRP4IOBufferMode,
|
||||||
InputRequiresFocus,
|
InputRequiresFocus,
|
||||||
NostalgiaPoke,
|
NostalgiaPoke,
|
||||||
|
NostalgiaTouchMode,
|
||||||
ForceBackBufferCount,
|
ForceBackBufferCount,
|
||||||
SDVXWindowedSubscreenSize,
|
SDVXWindowedSubscreenSize,
|
||||||
SDVXWindowedSubscreenPosition,
|
SDVXWindowedSubscreenPosition,
|
||||||
|
|||||||
+401
-45
@@ -1,5 +1,6 @@
|
|||||||
#include "signal.h"
|
#include "signal.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <future>
|
#include <future>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static decltype(MiniDumpWriteDump) *MiniDumpWriteDump_local = nullptr;
|
static decltype(MiniDumpWriteDump) *MiniDumpWriteDump_local = nullptr;
|
||||||
|
static LONG EXCEPTION_HANDLER_ACTIVE = 0;
|
||||||
|
|
||||||
namespace launcher::signal {
|
namespace launcher::signal {
|
||||||
|
|
||||||
@@ -47,7 +49,7 @@ static std::string control_code(DWORD dwCtrlType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string exception_code(struct _EXCEPTION_RECORD *ExceptionRecord) {
|
static std::string exception_code(const struct _EXCEPTION_RECORD *ExceptionRecord) {
|
||||||
switch (ExceptionRecord->ExceptionCode) {
|
switch (ExceptionRecord->ExceptionCode) {
|
||||||
V(EXCEPTION_ACCESS_VIOLATION);
|
V(EXCEPTION_ACCESS_VIOLATION);
|
||||||
V(EXCEPTION_ARRAY_BOUNDS_EXCEEDED);
|
V(EXCEPTION_ARRAY_BOUNDS_EXCEEDED);
|
||||||
@@ -77,6 +79,388 @@ static std::string exception_code(struct _EXCEPTION_RECORD *ExceptionRecord) {
|
|||||||
|
|
||||||
#undef V
|
#undef V
|
||||||
|
|
||||||
|
static const char *access_operation(ULONG_PTR operation) {
|
||||||
|
switch (operation) {
|
||||||
|
case 0:
|
||||||
|
return "read";
|
||||||
|
case 1:
|
||||||
|
return "write";
|
||||||
|
case 8:
|
||||||
|
return "execute";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *memory_state(DWORD state) {
|
||||||
|
switch (state) {
|
||||||
|
case MEM_COMMIT:
|
||||||
|
return "committed";
|
||||||
|
case MEM_FREE:
|
||||||
|
return "free";
|
||||||
|
case MEM_RESERVE:
|
||||||
|
return "reserved";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *memory_type(DWORD type) {
|
||||||
|
switch (type) {
|
||||||
|
case MEM_IMAGE:
|
||||||
|
return "image";
|
||||||
|
case MEM_MAPPED:
|
||||||
|
return "mapped";
|
||||||
|
case MEM_PRIVATE:
|
||||||
|
return "private";
|
||||||
|
default:
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *memory_protection(DWORD protection) {
|
||||||
|
switch (protection & 0xff) {
|
||||||
|
case PAGE_EXECUTE:
|
||||||
|
return "execute";
|
||||||
|
case PAGE_EXECUTE_READ:
|
||||||
|
return "execute-read";
|
||||||
|
case PAGE_EXECUTE_READWRITE:
|
||||||
|
return "execute-read-write";
|
||||||
|
case PAGE_EXECUTE_WRITECOPY:
|
||||||
|
return "execute-write-copy";
|
||||||
|
case PAGE_NOACCESS:
|
||||||
|
return "no-access";
|
||||||
|
case PAGE_READONLY:
|
||||||
|
return "read-only";
|
||||||
|
case PAGE_READWRITE:
|
||||||
|
return "read-write";
|
||||||
|
case PAGE_WRITECOPY:
|
||||||
|
return "write-copy";
|
||||||
|
default:
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_exception_parameters(const struct _EXCEPTION_RECORD *record) {
|
||||||
|
const auto parameter_count = record->NumberParameters <= EXCEPTION_MAXIMUM_PARAMETERS ?
|
||||||
|
record->NumberParameters : EXCEPTION_MAXIMUM_PARAMETERS;
|
||||||
|
|
||||||
|
log_warning("signal", "exception flags: 0x{:08x}, parameters: {}",
|
||||||
|
record->ExceptionFlags, parameter_count);
|
||||||
|
|
||||||
|
for (DWORD parameter = 0; parameter < parameter_count; parameter++) {
|
||||||
|
#ifdef _WIN64
|
||||||
|
log_warning("signal", "exception parameter[{}]: {:016x}",
|
||||||
|
parameter, record->ExceptionInformation[parameter]);
|
||||||
|
#else
|
||||||
|
log_warning("signal", "exception parameter[{}]: {:08x}",
|
||||||
|
parameter, record->ExceptionInformation[parameter]);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool query_memory(const void *address, MEMORY_BASIC_INFORMATION *memory) {
|
||||||
|
return VirtualQuery(address, memory, sizeof(*memory)) == sizeof(*memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_memory_information(const char *label, const MEMORY_BASIC_INFORMATION &memory) {
|
||||||
|
log_warning("signal",
|
||||||
|
"{}: base={}, size=0x{:x}, allocation_base={}, state={} (0x{:x}), "
|
||||||
|
"protect={} (0x{:x}), type={} (0x{:x})",
|
||||||
|
label,
|
||||||
|
fmt::ptr(memory.BaseAddress),
|
||||||
|
memory.RegionSize,
|
||||||
|
fmt::ptr(memory.AllocationBase),
|
||||||
|
memory_state(memory.State),
|
||||||
|
memory.State,
|
||||||
|
memory_protection(memory.Protect),
|
||||||
|
memory.Protect,
|
||||||
|
memory_type(memory.Type),
|
||||||
|
memory.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_memory_region(const char *label, const void *address) {
|
||||||
|
MEMORY_BASIC_INFORMATION memory {};
|
||||||
|
if (!query_memory(address, &memory)) {
|
||||||
|
log_warning("signal", "{}: VirtualQuery failed for {}: 0x{:08x}",
|
||||||
|
label, fmt::ptr(address), GetLastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_memory_information(label, memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_adjacent_memory_regions(const void *address) {
|
||||||
|
MEMORY_BASIC_INFORMATION current {};
|
||||||
|
if (!query_memory(address, ¤t)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto current_base = reinterpret_cast<uintptr_t>(current.BaseAddress);
|
||||||
|
if (current_base > 0) {
|
||||||
|
MEMORY_BASIC_INFORMATION previous {};
|
||||||
|
if (query_memory(reinterpret_cast<const void *>(current_base - 1), &previous)) {
|
||||||
|
log_memory_information("fault memory previous", previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto current_end = current_base + current.RegionSize;
|
||||||
|
if (current_end > current_base) {
|
||||||
|
MEMORY_BASIC_INFORMATION next {};
|
||||||
|
if (query_memory(reinterpret_cast<const void *>(current_end), &next)) {
|
||||||
|
log_memory_information("fault memory next", next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_exception_module(const void *address) {
|
||||||
|
MEMORY_BASIC_INFORMATION memory {};
|
||||||
|
if (!query_memory(address, &memory) || memory.Type != MEM_IMAGE || memory.AllocationBase == nullptr) {
|
||||||
|
log_warning("signal", "exception module: unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char module_path[MAX_PATH] {};
|
||||||
|
const auto module = static_cast<HMODULE>(memory.AllocationBase);
|
||||||
|
const auto module_base = reinterpret_cast<uintptr_t>(memory.AllocationBase);
|
||||||
|
const auto exception_address = reinterpret_cast<uintptr_t>(address);
|
||||||
|
const auto module_length = GetModuleFileNameA(module, module_path, sizeof(module_path));
|
||||||
|
|
||||||
|
if (module_length > 0) {
|
||||||
|
const std::string module_name(module_path, std::min<size_t>(module_length, sizeof(module_path)));
|
||||||
|
log_warning("signal", "exception module: '{}' base={}, rva=0x{:x}",
|
||||||
|
module_name, fmt::ptr(memory.AllocationBase), exception_address - module_base);
|
||||||
|
} else {
|
||||||
|
log_warning("signal", "exception module: base={}, rva=0x{:x}",
|
||||||
|
fmt::ptr(memory.AllocationBase), exception_address - module_base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_instruction_bytes(const void *address) {
|
||||||
|
MEMORY_BASIC_INFORMATION memory {};
|
||||||
|
if (!query_memory(address, &memory) ||
|
||||||
|
memory.State != MEM_COMMIT ||
|
||||||
|
(memory.Protect & (PAGE_GUARD | PAGE_NOACCESS)) != 0) {
|
||||||
|
log_warning("signal", "instruction bytes: unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t max_instruction_bytes = 16;
|
||||||
|
const auto region_end = reinterpret_cast<uintptr_t>(memory.BaseAddress) + memory.RegionSize;
|
||||||
|
const auto instruction_address = reinterpret_cast<uintptr_t>(address);
|
||||||
|
const auto available = region_end > instruction_address ? region_end - instruction_address : 0;
|
||||||
|
const auto byte_count = std::min(max_instruction_bytes, static_cast<size_t>(available));
|
||||||
|
uint8_t bytes[max_instruction_bytes] {};
|
||||||
|
SIZE_T bytes_read = 0;
|
||||||
|
if (byte_count == 0 ||
|
||||||
|
!ReadProcessMemory(GetCurrentProcess(), address, bytes, byte_count, &bytes_read)) {
|
||||||
|
log_warning("signal", "instruction bytes: read failed at {}: 0x{:08x}",
|
||||||
|
fmt::ptr(address), GetLastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string byte_string;
|
||||||
|
byte_string.reserve(bytes_read * 3);
|
||||||
|
|
||||||
|
for (size_t index = 0; index < bytes_read; index++) {
|
||||||
|
fmt::format_to(std::back_inserter(byte_string), "{:02x}{}",
|
||||||
|
bytes[index], index + 1 < bytes_read ? " " : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning("signal", "instruction bytes: {}", byte_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_stack_context(const CONTEXT *context) {
|
||||||
|
const auto *tib = reinterpret_cast<const NT_TIB *>(NtCurrentTeb());
|
||||||
|
const auto stack_base = reinterpret_cast<uintptr_t>(tib->StackBase);
|
||||||
|
const auto stack_limit = reinterpret_cast<uintptr_t>(tib->StackLimit);
|
||||||
|
#ifdef _WIN64
|
||||||
|
const auto stack_pointer = static_cast<uintptr_t>(context->Rsp);
|
||||||
|
#else
|
||||||
|
const auto stack_pointer = static_cast<uintptr_t>(context->Esp);
|
||||||
|
#endif
|
||||||
|
const auto stack_used = stack_base > stack_pointer ? stack_base - stack_pointer : 0;
|
||||||
|
const auto stack_remaining = stack_pointer > stack_limit ? stack_pointer - stack_limit : 0;
|
||||||
|
|
||||||
|
log_warning("signal", "stack: base={}, limit={}, pointer={}, used=0x{:x}, remaining=0x{:x}",
|
||||||
|
fmt::ptr(tib->StackBase),
|
||||||
|
fmt::ptr(tib->StackLimit),
|
||||||
|
fmt::ptr(reinterpret_cast<const void *>(stack_pointer)),
|
||||||
|
stack_used,
|
||||||
|
stack_remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_exception_context(struct _EXCEPTION_POINTERS *ExceptionInfo, DWORD last_error) {
|
||||||
|
const auto *record = ExceptionInfo->ExceptionRecord;
|
||||||
|
const auto *context = ExceptionInfo->ContextRecord;
|
||||||
|
|
||||||
|
log_warning("signal", "thread id: {}", GetCurrentThreadId());
|
||||||
|
log_warning("signal", "thread last error (GetLastError()) at exception: 0x{:08x}", last_error);
|
||||||
|
log_warning("signal", "exception address: {}", fmt::ptr(record->ExceptionAddress));
|
||||||
|
log_exception_module(record->ExceptionAddress);
|
||||||
|
log_instruction_bytes(record->ExceptionAddress);
|
||||||
|
log_exception_parameters(record);
|
||||||
|
|
||||||
|
if ((record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION ||
|
||||||
|
record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) &&
|
||||||
|
record->NumberParameters >= 2) {
|
||||||
|
const auto operation = record->ExceptionInformation[0];
|
||||||
|
const auto address = reinterpret_cast<const void *>(record->ExceptionInformation[1]);
|
||||||
|
log_warning("signal", "invalid memory access: {} at {}",
|
||||||
|
access_operation(operation), fmt::ptr(address));
|
||||||
|
log_memory_region("fault memory", address);
|
||||||
|
log_adjacent_memory_regions(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR && record->NumberParameters >= 3) {
|
||||||
|
log_warning("signal", "in-page error status: 0x{:08x}",
|
||||||
|
static_cast<DWORD>(record->ExceptionInformation[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_stack_context(context);
|
||||||
|
|
||||||
|
#ifdef _WIN64
|
||||||
|
log_warning("signal", "registers: rax={:016x} rbx={:016x} rcx={:016x} rdx={:016x}",
|
||||||
|
context->Rax, context->Rbx, context->Rcx, context->Rdx);
|
||||||
|
log_warning("signal", "registers: rsi={:016x} rdi={:016x} rbp={:016x} rsp={:016x}",
|
||||||
|
context->Rsi, context->Rdi, context->Rbp, context->Rsp);
|
||||||
|
log_warning("signal", "registers: r8={:016x} r9={:016x} r10={:016x} r11={:016x}",
|
||||||
|
context->R8, context->R9, context->R10, context->R11);
|
||||||
|
log_warning("signal", "registers: r12={:016x} r13={:016x} r14={:016x} r15={:016x}",
|
||||||
|
context->R12, context->R13, context->R14, context->R15);
|
||||||
|
log_warning("signal", "registers: rip={:016x} eflags={:08x}",
|
||||||
|
context->Rip, context->EFlags);
|
||||||
|
#else
|
||||||
|
log_warning("signal", "registers: eax={:08x} ebx={:08x} ecx={:08x} edx={:08x}",
|
||||||
|
context->Eax, context->Ebx, context->Ecx, context->Edx);
|
||||||
|
log_warning("signal", "registers: esi={:08x} edi={:08x} ebp={:08x} esp={:08x}",
|
||||||
|
context->Esi, context->Edi, context->Ebp, context->Esp);
|
||||||
|
log_warning("signal", "registers: eip={:08x} eflags={:08x}",
|
||||||
|
context->Eip, context->EFlags);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_minidump(struct _EXCEPTION_POINTERS *ExceptionInfo) {
|
||||||
|
if (MiniDumpWriteDump_local == nullptr) {
|
||||||
|
log_warning("signal", "minidump creation function not available, skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE minidump_file = CreateFileA(
|
||||||
|
"minidump.dmp",
|
||||||
|
GENERIC_WRITE,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
CREATE_ALWAYS,
|
||||||
|
FILE_ATTRIBUTE_NORMAL,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
if (minidump_file == INVALID_HANDLE_VALUE) {
|
||||||
|
log_warning("signal", "failed to create 'minidump.dmp' for minidump: 0x{:08x}",
|
||||||
|
GetLastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MINIDUMP_EXCEPTION_INFORMATION ExceptionParam {};
|
||||||
|
ExceptionParam.ThreadId = GetCurrentThreadId();
|
||||||
|
ExceptionParam.ExceptionPointers = ExceptionInfo;
|
||||||
|
ExceptionParam.ClientPointers = FALSE;
|
||||||
|
|
||||||
|
constexpr auto extended_type = static_cast<MINIDUMP_TYPE>(
|
||||||
|
MiniDumpWithUnloadedModules |
|
||||||
|
MiniDumpWithIndirectlyReferencedMemory |
|
||||||
|
MiniDumpWithProcessThreadData |
|
||||||
|
MiniDumpWithFullMemoryInfo |
|
||||||
|
MiniDumpWithThreadInfo |
|
||||||
|
MiniDumpIgnoreInaccessibleMemory);
|
||||||
|
|
||||||
|
auto written_type = extended_type;
|
||||||
|
auto minidump_written = MiniDumpWriteDump_local(
|
||||||
|
GetCurrentProcess(),
|
||||||
|
GetCurrentProcessId(),
|
||||||
|
minidump_file,
|
||||||
|
extended_type,
|
||||||
|
&ExceptionParam,
|
||||||
|
nullptr,
|
||||||
|
nullptr);
|
||||||
|
auto minidump_error = minidump_written ? ERROR_SUCCESS : GetLastError();
|
||||||
|
|
||||||
|
if (!minidump_written) {
|
||||||
|
log_warning("signal", "failed to write extended minidump: 0x{:08x}; retrying MiniDumpNormal",
|
||||||
|
minidump_error);
|
||||||
|
|
||||||
|
LARGE_INTEGER file_start {};
|
||||||
|
if (SetFilePointerEx(minidump_file, file_start, nullptr, FILE_BEGIN) &&
|
||||||
|
SetEndOfFile(minidump_file)) {
|
||||||
|
written_type = MiniDumpNormal;
|
||||||
|
minidump_written = MiniDumpWriteDump_local(
|
||||||
|
GetCurrentProcess(),
|
||||||
|
GetCurrentProcessId(),
|
||||||
|
minidump_file,
|
||||||
|
MiniDumpNormal,
|
||||||
|
&ExceptionParam,
|
||||||
|
nullptr,
|
||||||
|
nullptr);
|
||||||
|
minidump_error = minidump_written ? ERROR_SUCCESS : GetLastError();
|
||||||
|
} else {
|
||||||
|
minidump_error = GetLastError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LARGE_INTEGER minidump_size {};
|
||||||
|
const auto minidump_size_available = minidump_written &&
|
||||||
|
GetFileSizeEx(minidump_file, &minidump_size);
|
||||||
|
CloseHandle(minidump_file);
|
||||||
|
|
||||||
|
if (minidump_written) {
|
||||||
|
if (minidump_size_available) {
|
||||||
|
log_info("signal", "wrote minidump to 'minidump.dmp' (type=0x{:08x}, size={} bytes)",
|
||||||
|
static_cast<unsigned>(written_type), minidump_size.QuadPart);
|
||||||
|
} else {
|
||||||
|
log_info("signal", "wrote minidump to 'minidump.dmp' (type=0x{:08x})",
|
||||||
|
static_cast<unsigned>(written_type));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log_warning("signal", "failed to write 'minidump.dmp': 0x{:08x}", minidump_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_exception_chain(const struct _EXCEPTION_RECORD *record) {
|
||||||
|
constexpr size_t max_exception_chain_depth = 8;
|
||||||
|
auto *record_cause = record->ExceptionRecord;
|
||||||
|
size_t depth = 0;
|
||||||
|
|
||||||
|
while (record_cause != nullptr && depth < max_exception_chain_depth) {
|
||||||
|
struct _EXCEPTION_RECORD cause {};
|
||||||
|
SIZE_T bytes_read = 0;
|
||||||
|
if (!ReadProcessMemory(
|
||||||
|
GetCurrentProcess(),
|
||||||
|
record_cause,
|
||||||
|
&cause,
|
||||||
|
sizeof(cause),
|
||||||
|
&bytes_read) ||
|
||||||
|
bytes_read != sizeof(cause)) {
|
||||||
|
log_warning("signal", "failed to read exception chain record {} at {}: 0x{:08x}",
|
||||||
|
depth, fmt::ptr(record_cause), GetLastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning("signal", "caused by: {} at {}",
|
||||||
|
exception_code(&cause), fmt::ptr(cause.ExceptionAddress));
|
||||||
|
log_exception_parameters(&cause);
|
||||||
|
record_cause = cause.ExceptionRecord;
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record_cause != nullptr) {
|
||||||
|
log_warning("signal", "exception chain truncated after {} records", max_exception_chain_depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static BOOL WINAPI HandlerRoutine(DWORD dwCtrlType) {
|
static BOOL WINAPI HandlerRoutine(DWORD dwCtrlType) {
|
||||||
log_info("signal", "console ctrl handler called: {}", control_code(dwCtrlType));
|
log_info("signal", "console ctrl handler called: {}", control_code(dwCtrlType));
|
||||||
|
|
||||||
@@ -90,15 +474,23 @@ static BOOL WINAPI HandlerRoutine(DWORD dwCtrlType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo) {
|
static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo) {
|
||||||
|
const auto last_error = GetLastError();
|
||||||
|
|
||||||
// ignore signal if disabled or no exception info provided
|
// ignore signal if disabled or no exception info provided
|
||||||
if (!launcher::signal::DISABLE && ExceptionInfo != nullptr) {
|
if (!launcher::signal::DISABLE &&
|
||||||
|
ExceptionInfo != nullptr &&
|
||||||
|
ExceptionInfo->ExceptionRecord != nullptr &&
|
||||||
|
InterlockedCompareExchange(&EXCEPTION_HANDLER_ACTIVE, 1, 0) == 0) {
|
||||||
|
|
||||||
// get exception record
|
// get exception record
|
||||||
struct _EXCEPTION_RECORD *ExceptionRecord = ExceptionInfo->ExceptionRecord;
|
struct _EXCEPTION_RECORD *ExceptionRecord = ExceptionInfo->ExceptionRecord;
|
||||||
|
|
||||||
// print signal
|
// print signal
|
||||||
log_warning("signal", "exception raised: {}", exception_code(ExceptionRecord));
|
log_warning("signal", "exception raised: {}", exception_code(ExceptionRecord));
|
||||||
|
log_exception_context(ExceptionInfo, last_error);
|
||||||
|
|
||||||
|
// write the minidump before other diagnostics inspect potentially damaged state
|
||||||
|
write_minidump(ExceptionInfo);
|
||||||
|
|
||||||
switch (ExceptionRecord->ExceptionCode) {
|
switch (ExceptionRecord->ExceptionCode) {
|
||||||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||||
@@ -128,12 +520,11 @@ static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *Exception
|
|||||||
// (e.g., ACIO init hang due to RTSS)
|
// (e.g., ACIO init hang due to RTSS)
|
||||||
deferredlogs::dump_to_logger(true);
|
deferredlogs::dump_to_logger(true);
|
||||||
|
|
||||||
// walk the exception chain
|
// dump memory information before inspecting potentially damaged exception and stack data
|
||||||
struct _EXCEPTION_RECORD *record_cause = ExceptionRecord->ExceptionRecord;
|
memutils::show_available_memory();
|
||||||
while (record_cause != nullptr) {
|
|
||||||
log_warning("signal", "caused by: {}", exception_code(record_cause));
|
// walk the exception chain with bounded, checked reads
|
||||||
record_cause = record_cause->ExceptionRecord;
|
log_exception_chain(ExceptionRecord);
|
||||||
}
|
|
||||||
|
|
||||||
// print stacktrace
|
// print stacktrace
|
||||||
StackWalker sw;
|
StackWalker sw;
|
||||||
@@ -142,47 +533,12 @@ static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *Exception
|
|||||||
log_warning("signal", "failed to print callstack");
|
log_warning("signal", "failed to print callstack");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MiniDumpWriteDump_local != nullptr) {
|
|
||||||
HANDLE minidump_file = CreateFileA(
|
|
||||||
"minidump.dmp",
|
|
||||||
GENERIC_WRITE,
|
|
||||||
0,
|
|
||||||
nullptr,
|
|
||||||
CREATE_ALWAYS,
|
|
||||||
FILE_ATTRIBUTE_NORMAL,
|
|
||||||
nullptr);
|
|
||||||
|
|
||||||
if (minidump_file != INVALID_HANDLE_VALUE) {
|
|
||||||
MINIDUMP_EXCEPTION_INFORMATION ExceptionParam {};
|
|
||||||
ExceptionParam.ThreadId = GetCurrentThreadId();
|
|
||||||
ExceptionParam.ExceptionPointers = ExceptionInfo;
|
|
||||||
ExceptionParam.ClientPointers = FALSE;
|
|
||||||
|
|
||||||
MiniDumpWriteDump_local(
|
|
||||||
GetCurrentProcess(),
|
|
||||||
GetCurrentProcessId(),
|
|
||||||
minidump_file,
|
|
||||||
MiniDumpNormal,
|
|
||||||
&ExceptionParam,
|
|
||||||
nullptr,
|
|
||||||
nullptr);
|
|
||||||
|
|
||||||
CloseHandle(minidump_file);
|
|
||||||
} else {
|
|
||||||
log_warning("signal", "failed to create 'minidump.dmp' for minidump: 0x{:08x}",
|
|
||||||
GetLastError());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log_warning("signal", "minidump creation function not available, skipping");
|
|
||||||
}
|
|
||||||
|
|
||||||
// dump memory information
|
|
||||||
memutils::show_available_memory();
|
|
||||||
|
|
||||||
// this will stall all UI threads for this process
|
// this will stall all UI threads for this process
|
||||||
show_popup_for_crash();
|
show_popup_for_crash();
|
||||||
|
|
||||||
log_fatal("signal", "end");
|
log_fatal("signal", "end");
|
||||||
|
|
||||||
|
InterlockedExchange(&EXCEPTION_HANDLER_ACTIVE, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "eamuse.h"
|
#include "eamuse.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
@@ -10,7 +11,6 @@
|
|||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
#include "games/sdvx/sdvx.h"
|
#include "games/sdvx/sdvx.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/precise_timer.h"
|
|
||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "overlay/overlay.h"
|
#include "overlay/overlay.h"
|
||||||
@@ -335,7 +335,6 @@ void eamuse_coin_start_thread() {
|
|||||||
COIN_INPUT_THREAD = new std::thread([]() {
|
COIN_INPUT_THREAD = new std::thread([]() {
|
||||||
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
|
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
|
||||||
static bool COIN_INPUT_KEY_STATE = false;
|
static bool COIN_INPUT_KEY_STATE = false;
|
||||||
timeutils::PreciseSleepTimer timer;
|
|
||||||
while (COIN_INPUT_THREAD_ACTIVE) {
|
while (COIN_INPUT_THREAD_ACTIVE) {
|
||||||
|
|
||||||
// check input key
|
// check input key
|
||||||
@@ -355,7 +354,7 @@ void eamuse_coin_start_thread() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// once every two frames
|
// once every two frames
|
||||||
timer.sleep(1000 / 30);
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 30));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -409,7 +408,6 @@ void eamuse_pin_macro_start_thread() {
|
|||||||
size_t pin_index[2] = {PIN_MACRO_VALUES[0].length(), PIN_MACRO_VALUES[1].length()};
|
size_t pin_index[2] = {PIN_MACRO_VALUES[0].length(), PIN_MACRO_VALUES[1].length()};
|
||||||
|
|
||||||
std::optional<uint8_t> active_unit = std::nullopt;
|
std::optional<uint8_t> active_unit = std::nullopt;
|
||||||
timeutils::PreciseSleepTimer timer;
|
|
||||||
|
|
||||||
while (PIN_MACRO_THREAD_ACTIVE) {
|
while (PIN_MACRO_THREAD_ACTIVE) {
|
||||||
// wait for key press or auto-trigger
|
// wait for key press or auto-trigger
|
||||||
@@ -438,7 +436,7 @@ void eamuse_pin_macro_start_thread() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!active_unit.has_value()) {
|
if (!active_unit.has_value()) {
|
||||||
timer.sleep(20);
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -454,22 +452,22 @@ void eamuse_pin_macro_start_thread() {
|
|||||||
eamuse_set_keypad_overrides(unit, keypad_overrides[char_index]);
|
eamuse_set_keypad_overrides(unit, keypad_overrides[char_index]);
|
||||||
}
|
}
|
||||||
pin_index[unit]++;
|
pin_index[unit]++;
|
||||||
timer.sleep(100);
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
// clear
|
// clear
|
||||||
eamuse_set_keypad_overrides(unit, 0);
|
eamuse_set_keypad_overrides(unit, 0);
|
||||||
timer.sleep(50);
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
|
||||||
// end of PIN
|
// end of PIN
|
||||||
if (pin_index[unit] == PIN_MACRO_VALUES[unit].length()) {
|
if (pin_index[unit] == PIN_MACRO_VALUES[unit].length()) {
|
||||||
active_unit = std::nullopt;
|
active_unit = std::nullopt;
|
||||||
timer.sleep(120);
|
std::this_thread::sleep_for(std::chrono::milliseconds(120));
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.sleep(200);
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -675,7 +673,8 @@ void eamuse_autodetect_game() {
|
|||||||
eamuse_set_game("Beatmania IIDX");
|
eamuse_set_game("Beatmania IIDX");
|
||||||
else if (avs::game::is_model("J44") ||
|
else if (avs::game::is_model("J44") ||
|
||||||
avs::game::is_model("K44") ||
|
avs::game::is_model("K44") ||
|
||||||
avs::game::is_model("L44"))
|
avs::game::is_model("L44") ||
|
||||||
|
avs::game::is_model("T44"))
|
||||||
eamuse_set_game("Jubeat");
|
eamuse_set_game("Jubeat");
|
||||||
else if (avs::game::is_model("KDM"))
|
else if (avs::game::is_model("KDM"))
|
||||||
eamuse_set_game("Dance Evolution");
|
eamuse_set_game("Dance Evolution");
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
// enable touch functions - set version to windows 7
|
|
||||||
// mingw otherwise doesn't load touch stuff
|
|
||||||
#define _WIN32_WINNT 0x0601
|
|
||||||
|
|
||||||
#include "avs/game.h"
|
|
||||||
#include "wintouchemu.h"
|
|
||||||
#include "rawinput/touch.h"
|
|
||||||
#include "hooks/graphics/graphics.h"
|
|
||||||
|
|
||||||
#include "util/detour.h"
|
|
||||||
#include "util/logging.h"
|
|
||||||
|
|
||||||
#define TOUCH_SIMULATE_FAT_FINGERS 0
|
|
||||||
#define TOUCH_DEBUG_VERBOSE 0
|
|
||||||
|
|
||||||
#if TOUCH_DEBUG_VERBOSE
|
|
||||||
#define log_debug(module, format_str, ...) logger::push( \
|
|
||||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
|
||||||
#else
|
|
||||||
#define log_debug(module, format_str, ...)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace nativetouchhook {
|
|
||||||
|
|
||||||
static decltype(GetTouchInputInfo) *GetTouchInputInfo_orig = nullptr;
|
|
||||||
|
|
||||||
static void strip_contact_size(PTOUCHINPUT point) {
|
|
||||||
|
|
||||||
#if TOUCH_SIMULATE_FAT_FINGERS
|
|
||||||
point->dwMask |= 0x004;
|
|
||||||
point->cxContact = 80 * 100;
|
|
||||||
point->cyContact = 60 * 100;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// most monitors do not set TOUCHEVENTFMASK_CONTACTAREA, but for
|
|
||||||
// monitors that do set it, IIDX can get very confused (SDVX is not
|
|
||||||
// affected)
|
|
||||||
//
|
|
||||||
// while the test menu and the touch "glow" seem to work properly,
|
|
||||||
// interacting with subscreen menu items or entering PIN becomes
|
|
||||||
// very unpredictable
|
|
||||||
//
|
|
||||||
// to fix this, simply remove the contact area width and height
|
|
||||||
//
|
|
||||||
// note: test menu > I/O > touch test gives 5 numbers:
|
|
||||||
// n: x, y, w, h
|
|
||||||
// where
|
|
||||||
// n is the nth touch input since boot
|
|
||||||
// x, y are coordinates (center of finger)
|
|
||||||
// w, h are contact width and height
|
|
||||||
//
|
|
||||||
// when TOUCHEVENTFMASK_CONTACTAREA is not set, w/h will
|
|
||||||
// automatically be seen as 1x1, which works perfectly fine
|
|
||||||
|
|
||||||
log_debug(
|
|
||||||
"touch::native",
|
|
||||||
"[{}, {}] dwMask = 0x{:x}, cxContact = {}, cyContact = {}",
|
|
||||||
point->x / 100,
|
|
||||||
point->y / 100,
|
|
||||||
point->dwMask,
|
|
||||||
point->cxContact,
|
|
||||||
point->cyContact);
|
|
||||||
|
|
||||||
point->dwMask &= ~(0x004ul); // clear TOUCHEVENTFMASK_CONTACTAREA
|
|
||||||
point->cxContact = 0;
|
|
||||||
point->cyContact = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void flip_touch_points(PTOUCHINPUT point) {
|
|
||||||
point->x = rawinput::touch::DISPLAY_SIZE_X * 100 - point->x;
|
|
||||||
point->y = rawinput::touch::DISPLAY_SIZE_Y * 100 - point->y;
|
|
||||||
}
|
|
||||||
|
|
||||||
static BOOL WINAPI GetTouchInputInfoHook(
|
|
||||||
HTOUCHINPUT hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) {
|
|
||||||
|
|
||||||
// call the original fist
|
|
||||||
const auto result = GetTouchInputInfo_orig(hTouchInput, cInputs, pInputs, cbSize);
|
|
||||||
if (result == 0) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool flip_values = false;
|
|
||||||
if (avs::game::is_model("KFC") && rawinput::touch::DISPLAY_INITIALIZED) {
|
|
||||||
log_debug(
|
|
||||||
"touch::native", "DISPLAY_ORIENTATION = {}, DISPLAY_SIZE_X = {}, DISPLAY_SIZE_Y = {}",
|
|
||||||
rawinput::touch::DISPLAY_ORIENTATION,
|
|
||||||
rawinput::touch::DISPLAY_SIZE_X,
|
|
||||||
rawinput::touch::DISPLAY_SIZE_Y);
|
|
||||||
if (rawinput::touch::DISPLAY_ORIENTATION == DMDO_270) {
|
|
||||||
flip_values = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawinput::touch::INVERTED) {
|
|
||||||
flip_values = !flip_values;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < cInputs; i++) {
|
|
||||||
PTOUCHINPUT point = &pInputs[i];
|
|
||||||
|
|
||||||
if (avs::game::is_model("LDJ")) {
|
|
||||||
strip_contact_size(point);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flip_values) {
|
|
||||||
flip_touch_points(point);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void hook(HMODULE module) {
|
|
||||||
GetTouchInputInfo_orig = detour::iat_try("GetTouchInputInfo", GetTouchInputInfoHook, module);
|
|
||||||
if (GetTouchInputInfo_orig != nullptr) {
|
|
||||||
log_misc("touch::native", "GetTouchInputInfo hooked");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#include <windows.h>
|
|
||||||
|
|
||||||
namespace nativetouchhook {
|
|
||||||
void hook(HMODULE module);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -495,6 +495,14 @@ namespace wintouchemu {
|
|||||||
if (hWnd != nullptr && USE_MOUSE) {
|
if (hWnd != nullptr && USE_MOUSE) {
|
||||||
bool button_pressed = get_async_primary_mouse();
|
bool button_pressed = get_async_primary_mouse();
|
||||||
|
|
||||||
|
// while the subscreen overlay cannot accept the mouse, treat the button as
|
||||||
|
// released; this ends any active touch and prevents new mouse touch contacts
|
||||||
|
if (overlay::OVERLAY &&
|
||||||
|
overlay::OVERLAY->has_subscreen_touch_transform() &&
|
||||||
|
!overlay::OVERLAY->accepts_subscreen_mouse_input()) {
|
||||||
|
button_pressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
// if there was a touch event in the last 500 ms, don't insert new button presses
|
// if there was a touch event in the last 500 ms, don't insert new button presses
|
||||||
if (button_pressed && (now - last_touch_event) < 500) {
|
if (button_pressed && (now - last_touch_event) < 500) {
|
||||||
button_pressed = false;
|
button_pressed = false;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "games/io.h"
|
#include "games/io.h"
|
||||||
#include "games/gitadora/gitadora.h"
|
#include "games/gitadora/gitadora.h"
|
||||||
#include "games/iidx/iidx.h"
|
#include "games/iidx/iidx.h"
|
||||||
|
#include "games/nost/nost.h"
|
||||||
#include "games/popn/popn.h"
|
#include "games/popn/popn.h"
|
||||||
#include "games/rb/touch_debug.h"
|
#include "games/rb/touch_debug.h"
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
@@ -51,6 +52,7 @@
|
|||||||
#include "windows/sdvx_sub.h"
|
#include "windows/sdvx_sub.h"
|
||||||
#include "windows/keypad.h"
|
#include "windows/keypad.h"
|
||||||
#include "windows/log.h"
|
#include "windows/log.h"
|
||||||
|
#include "windows/nostalgia_touch_piano.h"
|
||||||
#include "windows/obs.h"
|
#include "windows/obs.h"
|
||||||
#include "windows/patch_manager.h"
|
#include "windows/patch_manager.h"
|
||||||
#include "windows/exitprompt.cpp"
|
#include "windows/exitprompt.cpp"
|
||||||
@@ -404,6 +406,12 @@ void overlay::SpiceOverlay::init() {
|
|||||||
window_fps->set_active(true);
|
window_fps->set_active(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cfg::CONFIGURATOR_STANDALONE &&
|
||||||
|
avs::game::is_model("PAN") && games::nost::ENABLE_TOUCH_MODE) {
|
||||||
|
window_nostalgia_touch_piano =
|
||||||
|
std::make_unique<overlay::windows::NostalgiaTouchPiano>(this);
|
||||||
|
}
|
||||||
|
|
||||||
// owned separately from `windows` so it is not part of the overlay window layer
|
// owned separately from `windows` so it is not part of the overlay window layer
|
||||||
window_main_menu = std::make_unique<overlay::windows::ExitPrompt>(this);
|
window_main_menu = std::make_unique<overlay::windows::ExitPrompt>(this);
|
||||||
|
|
||||||
@@ -556,13 +564,18 @@ void overlay::SpiceOverlay::new_frame() {
|
|||||||
const bool draw_fps_persistent = this->renderer != OverlayRenderer::SOFTWARE
|
const bool draw_fps_persistent = this->renderer != OverlayRenderer::SOFTWARE
|
||||||
&& this->window_fps->get_active();
|
&& this->window_fps->get_active();
|
||||||
|
|
||||||
|
const bool draw_nostalgia_touch_piano = this->renderer != OverlayRenderer::SOFTWARE
|
||||||
|
&& this->window_nostalgia_touch_piano
|
||||||
|
&& this->window_nostalgia_touch_piano->get_active();
|
||||||
|
|
||||||
// draw RB touch diagnostics
|
// draw RB touch diagnostics
|
||||||
const bool draw_rb_touch_debug = this->renderer != OverlayRenderer::SOFTWARE
|
const bool draw_rb_touch_debug = this->renderer != OverlayRenderer::SOFTWARE
|
||||||
&& games::rb::touch_debug_overlay_enabled();
|
&& games::rb::touch_debug_overlay_enabled();
|
||||||
|
|
||||||
// check if there is nothing to draw
|
// check if there is nothing to draw
|
||||||
this->has_pending_frame = false;
|
this->has_pending_frame = false;
|
||||||
if (!this->active && !draw_notifications && !draw_fps_persistent && !draw_rb_touch_debug) {
|
if (!this->active && !draw_notifications && !draw_fps_persistent &&
|
||||||
|
!draw_nostalgia_touch_piano && !draw_rb_touch_debug) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -596,7 +609,13 @@ void overlay::SpiceOverlay::new_frame() {
|
|||||||
for (auto &window : this->windows) {
|
for (auto &window : this->windows) {
|
||||||
window->build();
|
window->build();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draw_nostalgia_touch_piano) {
|
||||||
|
this->window_nostalgia_touch_piano->build();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->active) {
|
||||||
// draw the main menu on top of the overlay windows
|
// draw the main menu on top of the overlay windows
|
||||||
this->window_main_menu->build();
|
this->window_main_menu->build();
|
||||||
|
|
||||||
@@ -819,6 +838,10 @@ void overlay::SpiceOverlay::update() {
|
|||||||
// FPS window
|
// FPS window
|
||||||
this->window_fps->update();
|
this->window_fps->update();
|
||||||
|
|
||||||
|
if (this->window_nostalgia_touch_piano) {
|
||||||
|
this->window_nostalgia_touch_piano->update();
|
||||||
|
}
|
||||||
|
|
||||||
// main menu (owned separately from the overlay window layer)
|
// main menu (owned separately from the overlay window layer)
|
||||||
this->window_main_menu->update();
|
this->window_main_menu->update();
|
||||||
|
|
||||||
@@ -885,6 +908,17 @@ bool overlay::SpiceOverlay::get_active() {
|
|||||||
return this->active;
|
return this->active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool overlay::SpiceOverlay::is_subscreen_overlay_visible() {
|
||||||
|
return this->get_active() &&
|
||||||
|
this->window_sub != nullptr &&
|
||||||
|
this->window_sub->get_active();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool overlay::SpiceOverlay::accepts_subscreen_mouse_input() {
|
||||||
|
return this->is_subscreen_overlay_visible() &&
|
||||||
|
this->window_sub->is_mouse_hovered();
|
||||||
|
}
|
||||||
|
|
||||||
bool overlay::SpiceOverlay::has_focus() {
|
bool overlay::SpiceOverlay::has_focus() {
|
||||||
return this->get_active() && ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow);
|
return this->get_active() && ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ namespace overlay {
|
|||||||
// not part of `windows`: drawn/updated on the persistent layer (like
|
// not part of `windows`: drawn/updated on the persistent layer (like
|
||||||
// notifications), independent of the overlay's active state and input gates.
|
// notifications), independent of the overlay's active state and input gates.
|
||||||
std::unique_ptr<Window> window_fps;
|
std::unique_ptr<Window> window_fps;
|
||||||
|
std::unique_ptr<Window> window_nostalgia_touch_piano;
|
||||||
|
|
||||||
// not part of `windows`: the main menu / launcher. owned and drawn
|
// not part of `windows`: the main menu / launcher. owned and drawn
|
||||||
// separately from the overlay window layer; it drives the overlay's
|
// separately from the overlay window layer; it drives the overlay's
|
||||||
@@ -108,6 +109,7 @@ namespace overlay {
|
|||||||
void show_main_menu();
|
void show_main_menu();
|
||||||
void set_active(bool active);
|
void set_active(bool active);
|
||||||
bool get_active();
|
bool get_active();
|
||||||
|
bool accepts_subscreen_mouse_input();
|
||||||
bool has_focus();
|
bool has_focus();
|
||||||
bool hotkeys_triggered();
|
bool hotkeys_triggered();
|
||||||
|
|
||||||
@@ -138,21 +140,24 @@ namespace overlay {
|
|||||||
inline IDirect3DDevice9 *get_device() {
|
inline IDirect3DDevice9 *get_device() {
|
||||||
return this->device;
|
return this->device;
|
||||||
}
|
}
|
||||||
|
inline HWND get_window() {
|
||||||
|
return this->hWnd;
|
||||||
|
}
|
||||||
|
|
||||||
bool can_transform_touch_input() {
|
bool has_subscreen_touch_transform() {
|
||||||
return (this->subscreen_mouse_handler != nullptr);
|
return (this->subscreen_touch_transform != nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool transform_touch_point(LONG *x, LONG *y) {
|
bool transform_touch_point(LONG *x, LONG *y) {
|
||||||
if (this->get_active() && this->subscreen_mouse_handler) {
|
if (this->get_active() && this->subscreen_touch_transform) {
|
||||||
return this->subscreen_mouse_handler(x, y);
|
return this->subscreen_touch_transform(x, y);
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_subscreen_mouse_handler(const std::function<bool(LONG *, LONG *)> &f) {
|
void set_subscreen_touch_transform(const std::function<bool(LONG *, LONG *)> &f) {
|
||||||
this->subscreen_mouse_handler = f;
|
this->subscreen_touch_transform = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderer
|
// renderer
|
||||||
@@ -199,7 +204,7 @@ namespace overlay {
|
|||||||
|
|
||||||
std::vector<std::unique_ptr<Window>> windows;
|
std::vector<std::unique_ptr<Window>> windows;
|
||||||
|
|
||||||
std::function<bool(LONG *, LONG *)> subscreen_mouse_handler = nullptr;
|
std::function<bool(LONG *, LONG *)> subscreen_touch_transform = nullptr;
|
||||||
|
|
||||||
bool active = false;
|
bool active = false;
|
||||||
bool toggle_down = false;
|
bool toggle_down = false;
|
||||||
@@ -212,6 +217,7 @@ namespace overlay {
|
|||||||
// so render() never runs without a matching NewFrame.
|
// so render() never runs without a matching NewFrame.
|
||||||
bool has_pending_frame = false;
|
bool has_pending_frame = false;
|
||||||
|
|
||||||
|
bool is_subscreen_overlay_visible();
|
||||||
void init();
|
void init();
|
||||||
void add_font(const char* font, ImFontConfig* config, const ImWchar* glyphs);
|
void add_font(const char* font, ImFontConfig* config, const ImWchar* glyphs);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ void overlay::Window::build() {
|
|||||||
|
|
||||||
// check if active
|
// check if active
|
||||||
if (!this->active) {
|
if (!this->active) {
|
||||||
|
this->mouse_hovered = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,10 +107,18 @@ void overlay::Window::build() {
|
|||||||
// top spot. suppress that so only an explicit focus request (see below)
|
// top spot. suppress that so only an explicit focus request (see below)
|
||||||
// decides what comes to the front.
|
// decides what comes to the front.
|
||||||
const std::string window_id = this->title + "###" + to_string(this);
|
const std::string window_id = this->title + "###" + to_string(this);
|
||||||
if (ImGui::Begin(
|
const bool build_contents = ImGui::Begin(
|
||||||
window_id.c_str(),
|
window_id.c_str(),
|
||||||
&this->active,
|
&this->active,
|
||||||
this->flags | ImGuiWindowFlags_NoFocusOnAppearing)) {
|
this->flags | ImGuiWindowFlags_NoFocusOnAppearing);
|
||||||
|
|
||||||
|
// keep the window hovered while its invisible input button is held during a drag
|
||||||
|
this->mouse_hovered =
|
||||||
|
ImGui::IsWindowHovered(
|
||||||
|
ImGuiHoveredFlags_RootAndChildWindows |
|
||||||
|
ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
|
||||||
|
|
||||||
|
if (build_contents) {
|
||||||
|
|
||||||
// window attributes - init_pos / init_size are only honored once
|
// window attributes - init_pos / init_size are only honored once
|
||||||
// (ImGuiCond_Once), so compute them a single time instead of every frame
|
// (ImGuiCond_Once), so compute them a single time instead of every frame
|
||||||
@@ -154,6 +163,9 @@ void overlay::Window::build() {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
// raw content does not have an ImGui window to hover
|
||||||
|
this->mouse_hovered = false;
|
||||||
|
|
||||||
// add raw content
|
// add raw content
|
||||||
this->build_content();
|
this->build_content();
|
||||||
}
|
}
|
||||||
@@ -201,3 +213,7 @@ bool overlay::Window::get_active() {
|
|||||||
// now it depends on us
|
// now it depends on us
|
||||||
return this->active;
|
return this->active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool overlay::Window::is_mouse_hovered() {
|
||||||
|
return this->mouse_hovered;
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace overlay {
|
|||||||
void toggle_active();
|
void toggle_active();
|
||||||
void set_active(bool active);
|
void set_active(bool active);
|
||||||
bool get_active();
|
bool get_active();
|
||||||
|
bool is_mouse_hovered();
|
||||||
|
|
||||||
// raise this window to the top of the z-order on its next build. only
|
// raise this window to the top of the z-order on its next build. only
|
||||||
// call in response to an explicit user action (keyboard toggle, UI
|
// call in response to an explicit user action (keyboard toggle, UI
|
||||||
@@ -37,6 +38,7 @@ namespace overlay {
|
|||||||
// set when the window transitions to visible so build() raises it to the
|
// set when the window transitions to visible so build() raises it to the
|
||||||
// top of the z-order on the next frame
|
// top of the z-order on the next frame
|
||||||
bool request_focus = false;
|
bool request_focus = false;
|
||||||
|
bool mouse_hovered = false;
|
||||||
std::vector<Window*> children;
|
std::vector<Window*> children;
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
|
|||||||
@@ -82,9 +82,14 @@ namespace overlay::windows {
|
|||||||
ImGui::Text("Rendering");
|
ImGui::Text("Rendering");
|
||||||
|
|
||||||
// Media Type Selector
|
// Media Type Selector
|
||||||
int selectedMediaTypeIndex = selectedCamera->m_selectedMediaTypeIndex;
|
int selectedMediaTypeIndex = selectedCamera->GetSelectedMediaTypeIndex();
|
||||||
auto numMediaTypes = selectedCamera->m_mediaTypeInfos.size();
|
auto numMediaTypes = selectedCamera->m_mediaTypeInfos.size();
|
||||||
|
|
||||||
|
if (numMediaTypes == 0) {
|
||||||
|
ImGui::TextColored(ImVec4(1.f, 1.f, 0.f, 1.f), "%s", "No supported media types");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto selectedMediaTypeIndexChanged = ImGui::BeginCombo(
|
auto selectedMediaTypeIndexChanged = ImGui::BeginCombo(
|
||||||
"Media Type", selectedCamera->m_mediaTypeInfos.at(selectedMediaTypeIndex).description.c_str()
|
"Media Type", selectedCamera->m_mediaTypeInfos.at(selectedMediaTypeIndex).description.c_str()
|
||||||
);
|
);
|
||||||
@@ -103,9 +108,9 @@ namespace overlay::windows {
|
|||||||
ImGui::EndCombo();
|
ImGui::EndCombo();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedMediaTypeIndexChanged && selectedMediaTypeIndex != selectedCamera->m_selectedMediaTypeIndex) {
|
if (selectedMediaTypeIndexChanged && selectedMediaTypeIndex != selectedCamera->GetSelectedMediaTypeIndex()) {
|
||||||
selectedCamera->m_useAutoMediaType = false;
|
selectedCamera->m_useAutoMediaType = false;
|
||||||
selectedCamera->ChangeMediaType(selectedCamera->m_mediaTypeInfos.at(selectedMediaTypeIndex).p_mediaType);
|
selectedCamera->RequestMediaType(selectedCamera->m_mediaTypeInfos.at(selectedMediaTypeIndex).p_mediaType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto media type
|
// Auto media type
|
||||||
@@ -114,13 +119,14 @@ namespace overlay::windows {
|
|||||||
if (ImGui::Checkbox("Auto##MediaType", &isAutoMediaType)) {
|
if (ImGui::Checkbox("Auto##MediaType", &isAutoMediaType)) {
|
||||||
selectedCamera->m_useAutoMediaType = isAutoMediaType;
|
selectedCamera->m_useAutoMediaType = isAutoMediaType;
|
||||||
if (isAutoMediaType) {
|
if (isAutoMediaType) {
|
||||||
selectedCamera->ChangeMediaType(selectedCamera->m_pAutoMediaType);
|
selectedCamera->RequestMediaType(selectedCamera->m_pAutoMediaType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw mode
|
// Draw mode
|
||||||
int selectedDrawModeIndex = selectedCamera->m_drawMode;
|
const auto drawMode = selectedCamera->m_drawMode.load();
|
||||||
if (ImGui::BeginCombo("Draw Mode", DRAW_MODE_LABELS[selectedCamera->m_drawMode].c_str())) {
|
int selectedDrawModeIndex = drawMode;
|
||||||
|
if (ImGui::BeginCombo("Draw Mode", DRAW_MODE_LABELS[drawMode].c_str())) {
|
||||||
for (size_t i = 0; i < DRAW_MODE_SIZE; i++) {
|
for (size_t i = 0; i < DRAW_MODE_SIZE; i++) {
|
||||||
const bool is_selected = (selectedDrawModeIndex == (int) i);
|
const bool is_selected = (selectedDrawModeIndex == (int) i);
|
||||||
if (ImGui::Selectable(DRAW_MODE_LABELS[i].c_str(), is_selected)) {
|
if (ImGui::Selectable(DRAW_MODE_LABELS[i].c_str(), is_selected)) {
|
||||||
@@ -142,15 +148,25 @@ namespace overlay::windows {
|
|||||||
"Letterbox to 4:3: Like Letterbox, but target 4:3"
|
"Letterbox to 4:3: Like Letterbox, but target 4:3"
|
||||||
);
|
);
|
||||||
|
|
||||||
if (selectedDrawModeIndex != selectedCamera->m_drawMode) {
|
if (selectedDrawModeIndex != selectedCamera->m_drawMode.load()) {
|
||||||
selectedCamera->m_drawMode = (LocalCameraDrawMode)selectedDrawModeIndex;
|
selectedCamera->m_drawMode.store((LocalCameraDrawMode)selectedDrawModeIndex);
|
||||||
selectedCamera->UpdateDrawRect();
|
selectedCamera->UpdateDrawRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::AlignTextToFramePadding();
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::Checkbox("Horizontal Flip", &selectedCamera->m_flipHorizontal);
|
bool flipHorizontal = selectedCamera->m_flipHorizontal.load();
|
||||||
|
if (ImGui::Checkbox("Horizontal Flip", &flipHorizontal)) {
|
||||||
|
selectedCamera->m_flipHorizontal.store(flipHorizontal);
|
||||||
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Checkbox("Vertical Flip", &selectedCamera->m_flipVertical);
|
bool flipVertical = selectedCamera->m_flipVertical.load();
|
||||||
|
if (ImGui::Checkbox("Vertical Flip", &flipVertical)) {
|
||||||
|
selectedCamera->m_flipVertical.store(flipVertical);
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::HelpMarker(
|
||||||
|
"Flips are done by the CPU and can be very expensive, potentially causing frame drops! "
|
||||||
|
"Prefer to use camera's built-in flip if available.");
|
||||||
|
|
||||||
// Camera control parameters
|
// Camera control parameters
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ namespace overlay::windows {
|
|||||||
static const std::vector<std::pair<const char *, ControllerPage>> CONTROLLER_PAGE_GROUPS = {
|
static const std::vector<std::pair<const char *, ControllerPage>> CONTROLLER_PAGE_GROUPS = {
|
||||||
{ "Buttons", ControllerPage::CONTROLLER_PAGE_BUTTONS },
|
{ "Buttons", ControllerPage::CONTROLLER_PAGE_BUTTONS },
|
||||||
{ "Keypads", ControllerPage::CONTROLLER_PAGE_KEYPADS },
|
{ "Keypads", ControllerPage::CONTROLLER_PAGE_KEYPADS },
|
||||||
|
{ "Modifiers", ControllerPage::CONTROLLER_PAGE_MODIFIERS },
|
||||||
{ "Analogs", ControllerPage::CONTROLLER_PAGE_ANALOGS },
|
{ "Analogs", ControllerPage::CONTROLLER_PAGE_ANALOGS },
|
||||||
{ "Overlay", ControllerPage::CONTROLLER_PAGE_OVERLAY },
|
{ "Overlay", ControllerPage::CONTROLLER_PAGE_OVERLAY },
|
||||||
{ "Lights", ControllerPage::CONTROLLER_PAGE_LIGHTS },
|
{ "Lights", ControllerPage::CONTROLLER_PAGE_LIGHTS },
|
||||||
@@ -251,7 +252,7 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Renders an arrow-less, non-collapsible left-nav header row, highlighted when
|
// Renders an arrow-less, non-collapsible left-nav header row, highlighted when
|
||||||
// active. Returns true if the row was clicked this frame; callers apply their
|
// active. Returns true if the row was activated this frame; callers apply their
|
||||||
// own selection side effects.
|
// own selection side effects.
|
||||||
bool Config::build_nav_header(const char *label, bool active) {
|
bool Config::build_nav_header(const char *label, bool active) {
|
||||||
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf;
|
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf;
|
||||||
@@ -282,7 +283,7 @@ namespace overlay::windows {
|
|||||||
ImGui::PopStyleColor(colors_pushed);
|
ImGui::PopStyleColor(colors_pushed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ImGui::IsItemClicked();
|
return ImGui::IsItemActivated();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::build_options_tab(float page_offset) {
|
void Config::build_options_tab(float page_offset) {
|
||||||
@@ -616,6 +617,21 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ControllerPage::CONTROLLER_PAGE_MODIFIERS: {
|
||||||
|
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Button Modifiers");
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::TextWrapped(
|
||||||
|
"Bind modifier buttons below, then open Edit properties on any button binding "
|
||||||
|
"(except MIDI) to select the required modifiers. This allows you to configure "
|
||||||
|
"button combinations.\n\n"
|
||||||
|
"For example, if you bind the Start button as Modifier 1, and then bind "
|
||||||
|
"the A button for Service Coin and set it to require Modifier 1, you press "
|
||||||
|
"Start + A to trigger Service Coin.");
|
||||||
|
ImGui::TextUnformatted("");
|
||||||
|
this->build_buttons(
|
||||||
|
"Modifier", games::get_buttons_modifiers(this->games_selected_name));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case ControllerPage::CONTROLLER_PAGE_ANALOGS: {
|
case ControllerPage::CONTROLLER_PAGE_ANALOGS: {
|
||||||
|
|
||||||
// help text for binding analog, if the game has one
|
// help text for binding analog, if the game has one
|
||||||
@@ -1256,7 +1272,8 @@ namespace overlay::windows {
|
|||||||
if (open_edit) {
|
if (open_edit) {
|
||||||
ImGui::OpenPopup(edit_name.c_str());
|
ImGui::OpenPopup(edit_name.c_str());
|
||||||
}
|
}
|
||||||
edit_button_popup(edit_name, button_display, button, button_velocity, alt_index);
|
edit_button_popup(
|
||||||
|
edit_name, button_display, button, button_velocity, alt_index, name != "Modifier");
|
||||||
|
|
||||||
// clean up
|
// clean up
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
@@ -1275,6 +1292,7 @@ namespace overlay::windows {
|
|||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
button->setInvert(false);
|
button->setInvert(false);
|
||||||
|
button->setModifierMask(0);
|
||||||
button->setLastState(GameAPI::Buttons::BUTTON_NOT_PRESSED);
|
button->setLastState(GameAPI::Buttons::BUTTON_NOT_PRESSED);
|
||||||
button->setLastVelocity(0);
|
button->setLastVelocity(0);
|
||||||
button->setTemporary(false);
|
button->setTemporary(false);
|
||||||
@@ -1356,8 +1374,9 @@ namespace overlay::windows {
|
|||||||
if (check_devices) {
|
if (check_devices) {
|
||||||
// iterate updated devices
|
// iterate updated devices
|
||||||
auto updated_devices = RI_MGR->devices_get_updated();
|
auto updated_devices = RI_MGR->devices_get_updated();
|
||||||
|
bool binding_finished = false;
|
||||||
for (auto device : updated_devices) {
|
for (auto device : updated_devices) {
|
||||||
std::lock_guard<std::mutex> lock(*device->mutex);
|
std::unique_lock<std::mutex> lock(*device->mutex);
|
||||||
switch (device->type) {
|
switch (device->type) {
|
||||||
case rawinput::MOUSE: {
|
case rawinput::MOUSE: {
|
||||||
auto mouse = device->mouseInfo;
|
auto mouse = device->mouseInfo;
|
||||||
@@ -1369,12 +1388,13 @@ namespace overlay::windows {
|
|||||||
button->setDeviceIdentifier(device->name);
|
button->setDeviceIdentifier(device->name);
|
||||||
button->setVKey(static_cast<unsigned short>(i));
|
button->setVKey(static_cast<unsigned short>(i));
|
||||||
button->setAnalogType(BAT_NONE);
|
button->setAnalogType(BAT_NONE);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button, alt_index - 1);
|
games_list[games_selected], *button, alt_index - 1);
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1393,7 +1413,7 @@ namespace overlay::windows {
|
|||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
buttons_many_index = -1;
|
buttons_many_index = -1;
|
||||||
buttons_many_active = false;
|
buttons_many_active = false;
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1401,12 +1421,13 @@ namespace overlay::windows {
|
|||||||
button->setDeviceIdentifier(device->name);
|
button->setDeviceIdentifier(device->name);
|
||||||
button->setVKey(vkey);
|
button->setVKey(vkey);
|
||||||
button->setAnalogType(BAT_NONE);
|
button->setAnalogType(BAT_NONE);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button, alt_index - 1);
|
games_list[games_selected], *button, alt_index - 1);
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1536,13 +1557,14 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -1581,6 +1603,7 @@ namespace overlay::windows {
|
|||||||
button->setDebounceUp(0.0);
|
button->setDebounceUp(0.0);
|
||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
// same idea as setMidiVKey - keep velocity threshold consistent
|
// same idea as setMidiVKey - keep velocity threshold consistent
|
||||||
button->setVelocityThreshold(
|
button->setVelocityThreshold(
|
||||||
device->midiInfo->v2_velocity_threshold[button->getVKey()]);
|
device->midiInfo->v2_velocity_threshold[button->getVKey()]);
|
||||||
@@ -1590,7 +1613,7 @@ namespace overlay::windows {
|
|||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1608,13 +1631,14 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1635,13 +1659,14 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1662,13 +1687,14 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1688,13 +1714,14 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1709,13 +1736,14 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1739,13 +1767,14 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1755,6 +1784,13 @@ namespace overlay::windows {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
lock.unlock();
|
||||||
|
if (binding_finished) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (binding_finished) {
|
||||||
|
RI_MGR->devices_midi_freeze(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1808,6 +1844,7 @@ namespace overlay::windows {
|
|||||||
if (RI_MGR->XINPUT_MGR->get_any_button_pressed(xinput)) {
|
if (RI_MGR->XINPUT_MGR->get_any_button_pressed(xinput)) {
|
||||||
button->setDeviceIdentifier(xinput::get_device_desc(xinput.player));
|
button->setDeviceIdentifier(xinput::get_device_desc(xinput.player));
|
||||||
button->setVKey(static_cast<uint16_t>(xinput.button));
|
button->setVKey(static_cast<uint16_t>(xinput.button));
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button, alt_index - 1);
|
games_list[games_selected], *button, alt_index - 1);
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
@@ -1864,6 +1901,7 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
@@ -1882,27 +1920,103 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Config::build_modifier_picker(Button &button) {
|
||||||
|
bool changed = false;
|
||||||
|
auto modifier_mask = button.getModifierMask();
|
||||||
|
|
||||||
|
// collect live states and build the closed dropdown summary
|
||||||
|
uint8_t pressed_modifier_mask = 0;
|
||||||
|
std::string modifier_preview;
|
||||||
|
auto *modifier_buttons = games::get_buttons_modifiers(this->games_selected_name);
|
||||||
|
for (uint8_t index = 0; index < games::ModifierButtons::Size; index++) {
|
||||||
|
const uint8_t modifier_bit = UINT8_C(1) << index;
|
||||||
|
if (modifier_buttons && index < modifier_buttons->size() &&
|
||||||
|
GameAPI::Buttons::getState(RI_MGR, modifier_buttons->at(index))) {
|
||||||
|
pressed_modifier_mask |= modifier_bit;
|
||||||
|
}
|
||||||
|
if ((modifier_mask & modifier_bit) != 0) {
|
||||||
|
modifier_preview += modifier_preview.empty() ? "Mod " : "+";
|
||||||
|
modifier_preview += std::to_string(index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (modifier_preview.empty()) {
|
||||||
|
modifier_preview = "None";
|
||||||
|
}
|
||||||
|
|
||||||
|
// color the combo when all requirements are held, but keep its label unchanged
|
||||||
|
const bool all_modifiers_pressed = modifier_mask != 0 &&
|
||||||
|
(pressed_modifier_mask & modifier_mask) == modifier_mask;
|
||||||
|
if (all_modifiers_pressed) {
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, TEXT_COLOR_GREEN);
|
||||||
|
}
|
||||||
|
const bool combo_open = ImGui::BeginCombo("##Modifiers", modifier_preview.c_str());
|
||||||
|
if (all_modifiers_pressed) {
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// update requirements while highlighting modifier sources that are currently pressed
|
||||||
|
if (combo_open) {
|
||||||
|
for (uint8_t index = 0; index < games::ModifierButtons::Size; index++) {
|
||||||
|
const uint8_t modifier_bit = UINT8_C(1) << index;
|
||||||
|
bool required = (modifier_mask & modifier_bit) != 0;
|
||||||
|
const auto label = fmt::format("Modifier {}", index + 1);
|
||||||
|
const bool modifier_pressed = (pressed_modifier_mask & modifier_bit) != 0;
|
||||||
|
if (modifier_pressed) {
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, TEXT_COLOR_GREEN);
|
||||||
|
}
|
||||||
|
const bool requirement_changed = ImGui::Checkbox(label.c_str(), &required);
|
||||||
|
if (modifier_pressed) {
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
if (requirement_changed) {
|
||||||
|
modifier_mask ^= modifier_bit;
|
||||||
|
button.setModifierMask(modifier_mask);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||||
|
ImGui::TextUnformatted("Modifiers");
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
void Config::edit_button_popup(
|
void Config::edit_button_popup(
|
||||||
const std::string &edit_name,
|
const std::string &edit_name,
|
||||||
const std::string &button_display,
|
const std::string &button_display,
|
||||||
Button *button,
|
Button *button,
|
||||||
const float button_velocity,
|
const float button_velocity,
|
||||||
const int alt_index) {
|
const int alt_index,
|
||||||
|
const bool allow_modifiers) {
|
||||||
|
|
||||||
const auto button_state = GameAPI::Buttons::getState(RI_MGR, *button, false);
|
const auto button_state = GameAPI::Buttons::getState(RI_MGR, *button, false);
|
||||||
if (ImGui::BeginPopupModal(edit_name.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
|
if (ImGui::BeginPopupModal(edit_name.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
auto device = RI_MGR->devices_get(button->getDeviceIdentifier());
|
auto device = RI_MGR->devices_get(button->getDeviceIdentifier());
|
||||||
|
|
||||||
|
auto switch_device = [&](const std::string &device_identifier) {
|
||||||
|
button->setDeviceIdentifier(device_identifier);
|
||||||
|
button->setAnalogType(ButtonAnalogType::BAT_NONE);
|
||||||
|
button->setDebounceUp(0.0);
|
||||||
|
button->setDebounceDown(0.0);
|
||||||
|
button->setBatThreshold(0);
|
||||||
|
button->setVelocityThreshold(0);
|
||||||
|
button->setVKey(0);
|
||||||
|
button->setInvert(false);
|
||||||
|
button->setModifierMask(0);
|
||||||
|
dirty = true;
|
||||||
|
};
|
||||||
|
|
||||||
// binding
|
// binding
|
||||||
ImGui::Text("Binding");
|
ImGui::Text("Binding");
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
// combo for devices
|
// combo for devices
|
||||||
std::string device_desc = (device != nullptr) ? device->desc : "Empty (Naive)";
|
std::string device_desc = (device != nullptr) ? device->desc : "Empty (Naive)";
|
||||||
if (ImGui::BeginCombo("Device Identifier", device_desc.c_str())) {
|
if (ImGui::BeginCombo("Device Identifier", device_desc.c_str())) {
|
||||||
if (ImGui::Selectable("Empty (Naive)", button->isNaive())) {
|
if (ImGui::Selectable("Empty (Naive)", button->isNaive()) && !button->isNaive()) {
|
||||||
button->setDeviceIdentifier("");
|
switch_device("");
|
||||||
dirty = true;
|
|
||||||
}
|
}
|
||||||
if (button->isNaive()) {
|
if (button->isNaive()) {
|
||||||
ImGui::SetItemDefaultFocus();
|
ImGui::SetItemDefaultFocus();
|
||||||
@@ -1910,17 +2024,8 @@ namespace overlay::windows {
|
|||||||
for (auto &device : RI_MGR->devices_get()) {
|
for (auto &device : RI_MGR->devices_get()) {
|
||||||
bool selected = button->getDeviceIdentifier() == device.name.c_str();
|
bool selected = button->getDeviceIdentifier() == device.name.c_str();
|
||||||
const auto device_desc = fmt::format("{}##{}", device.desc, device.name);
|
const auto device_desc = fmt::format("{}##{}", device.desc, device.name);
|
||||||
if (ImGui::Selectable(device_desc.c_str(), selected)) {
|
if (ImGui::Selectable(device_desc.c_str(), selected) && !selected) {
|
||||||
button->setDeviceIdentifier(device.name);
|
switch_device(device.name);
|
||||||
// reset controls when switching devices
|
|
||||||
button->setAnalogType(ButtonAnalogType::BAT_NONE);
|
|
||||||
button->setDebounceUp(0.0);
|
|
||||||
button->setDebounceDown(0.0);
|
|
||||||
button->setBatThreshold(0);
|
|
||||||
button->setVelocityThreshold(0);
|
|
||||||
button->setVKey(0);
|
|
||||||
button->setInvert(false);
|
|
||||||
dirty = true;
|
|
||||||
}
|
}
|
||||||
if (selected) {
|
if (selected) {
|
||||||
ImGui::SetItemDefaultFocus();
|
ImGui::SetItemDefaultFocus();
|
||||||
@@ -1928,6 +2033,7 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
ImGui::EndCombo();
|
ImGui::EndCombo();
|
||||||
}
|
}
|
||||||
|
device = RI_MGR->devices_get(button->getDeviceIdentifier());
|
||||||
|
|
||||||
// analog type (only for HID)
|
// analog type (only for HID)
|
||||||
const auto bat = button->getAnalogType();
|
const auto bat = button->getAnalogType();
|
||||||
@@ -2219,21 +2325,22 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// modifiers
|
||||||
|
{
|
||||||
|
const bool is_midi = device != nullptr && device->type == rawinput::MIDI;
|
||||||
|
if (allow_modifiers && button->isValid() && !is_midi) {
|
||||||
|
dirty |= build_modifier_picker(*button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// preview
|
// preview
|
||||||
if (!button_display.empty()) {
|
if (!button_display.empty()) {
|
||||||
ImGui::TextUnformatted("\nPreview");
|
ImGui::TextUnformatted("\nPreview");
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (button_state == GameAPI::Buttons::State::BUTTON_PRESSED) {
|
if (button_state == GameAPI::Buttons::State::BUTTON_PRESSED) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 0.7f, 0.f, 1.f));
|
ImGui::PushStyleColor(ImGuiCol_Text, TEXT_COLOR_GREEN);
|
||||||
}
|
|
||||||
// this doesn't account for utf-8 but whatever
|
|
||||||
if (button_display.size() >= 40) {
|
|
||||||
ImGui::Text("%.37s...", button_display.c_str());
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::HelpMarker(button_display.c_str());
|
|
||||||
} else {
|
|
||||||
ImGui::TextUnformatted(button_display.c_str());
|
|
||||||
}
|
}
|
||||||
|
ImGui::TextTruncated(button_display, ImGui::GetContentRegionAvail().x);
|
||||||
ImGui::TextUnformatted("\n");
|
ImGui::TextUnformatted("\n");
|
||||||
if (button_state == GameAPI::Buttons::State::BUTTON_PRESSED) {
|
if (button_state == GameAPI::Buttons::State::BUTTON_PRESSED) {
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
@@ -2242,8 +2349,10 @@ namespace overlay::windows {
|
|||||||
ImGui::TextUnformatted("");
|
ImGui::TextUnformatted("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// options
|
// options
|
||||||
ImGui::Text("Options");
|
ImGui::TextUnformatted("Options");
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
// check for debounce
|
// check for debounce
|
||||||
if (button->getDebounceUp() || button->getDebounceDown()
|
if (button->getDebounceUp() || button->getDebounceDown()
|
||||||
@@ -2362,6 +2471,7 @@ namespace overlay::windows {
|
|||||||
ImGui::ProgressBar(button_velocity);
|
ImGui::ProgressBar(button_velocity);
|
||||||
} else {
|
} else {
|
||||||
ImGui::Text("State");
|
ImGui::Text("State");
|
||||||
|
ImGui::Separator();
|
||||||
ImGui::ProgressBar(button_velocity);
|
ImGui::ProgressBar(button_velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5482,6 +5592,12 @@ namespace overlay::windows {
|
|||||||
tmpl.buttons.emplace_back(btn);
|
tmpl.buttons.emplace_back(btn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
auto *modifier_buttons = games::get_buttons_modifiers(this->games_selected_name);
|
||||||
|
if (modifier_buttons) {
|
||||||
|
for (auto &modifier : *modifier_buttons) {
|
||||||
|
tmpl.modifier_buttons.emplace_back(modifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// capture keypad buttons
|
// capture keypad buttons
|
||||||
auto *keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
|
auto *keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
|
||||||
@@ -5537,6 +5653,17 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clear all modifier buttons
|
||||||
|
auto *modifier_buttons = games::get_buttons_modifiers(this->games_selected_name);
|
||||||
|
if (modifier_buttons) {
|
||||||
|
for (auto &modifier : *modifier_buttons) {
|
||||||
|
for (int ai = (int)modifier.getAlternatives().size() - 1; ai >= 0; ai--) {
|
||||||
|
clear_button(&modifier.getAlternatives()[ai], ai + 1);
|
||||||
|
}
|
||||||
|
clear_button(&modifier, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// clear all keypad buttons
|
// clear all keypad buttons
|
||||||
auto *keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
|
auto *keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
|
||||||
if (keypad_buttons) {
|
if (keypad_buttons) {
|
||||||
@@ -5630,6 +5757,7 @@ namespace overlay::windows {
|
|||||||
btn.setDebounceDown(entry->debounce_down);
|
btn.setDebounceDown(entry->debounce_down);
|
||||||
btn.setBatThreshold(entry->bat_threshold);
|
btn.setBatThreshold(entry->bat_threshold);
|
||||||
btn.setVelocityThreshold(entry->velocity_threshold);
|
btn.setVelocityThreshold(entry->velocity_threshold);
|
||||||
|
btn.setModifierMask(entry->modifier_mask);
|
||||||
::Config::getInstance().updateBinding(game, btn, -1);
|
::Config::getInstance().updateBinding(game, btn, -1);
|
||||||
} else {
|
} else {
|
||||||
Button alt_btn(btn.getName());
|
Button alt_btn(btn.getName());
|
||||||
@@ -5641,6 +5769,7 @@ namespace overlay::windows {
|
|||||||
alt_btn.setDebounceDown(entry->debounce_down);
|
alt_btn.setDebounceDown(entry->debounce_down);
|
||||||
alt_btn.setBatThreshold(entry->bat_threshold);
|
alt_btn.setBatThreshold(entry->bat_threshold);
|
||||||
alt_btn.setVelocityThreshold(entry->velocity_threshold);
|
alt_btn.setVelocityThreshold(entry->velocity_threshold);
|
||||||
|
alt_btn.setModifierMask(entry->modifier_mask);
|
||||||
alt_btn.setTemporary(true);
|
alt_btn.setTemporary(true);
|
||||||
btn.getAlternatives().push_back(alt_btn);
|
btn.getAlternatives().push_back(alt_btn);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
@@ -5659,6 +5788,11 @@ namespace overlay::windows {
|
|||||||
if (this->apply_buttons) {
|
if (this->apply_buttons) {
|
||||||
apply_buttons(tmpl.buttons, games::get_buttons(this->games_selected_name));
|
apply_buttons(tmpl.buttons, games::get_buttons(this->games_selected_name));
|
||||||
}
|
}
|
||||||
|
if (this->apply_modifiers) {
|
||||||
|
apply_buttons(
|
||||||
|
tmpl.modifier_buttons,
|
||||||
|
games::get_buttons_modifiers(this->games_selected_name));
|
||||||
|
}
|
||||||
if (this->apply_keypads) {
|
if (this->apply_keypads) {
|
||||||
apply_buttons(tmpl.keypad_buttons, games::get_buttons_keypads(this->games_selected_name));
|
apply_buttons(tmpl.keypad_buttons, games::get_buttons_keypads(this->games_selected_name));
|
||||||
}
|
}
|
||||||
@@ -5900,7 +6034,7 @@ namespace overlay::windows {
|
|||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::HelpMarker(
|
ImGui::HelpMarker(
|
||||||
"Clears all game button, analog, and light bindings.");
|
"Clears all game button, modifier, keypad, analog, and light bindings.");
|
||||||
if (this->all_cleared) {
|
if (this->all_cleared) {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::TextUnformatted("Done.");
|
ImGui::TextUnformatted("Done.");
|
||||||
@@ -5915,6 +6049,8 @@ namespace overlay::windows {
|
|||||||
bool selection_changed = false;
|
bool selection_changed = false;
|
||||||
selection_changed |= ImGui::Checkbox("Buttons", &this->apply_buttons);
|
selection_changed |= ImGui::Checkbox("Buttons", &this->apply_buttons);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
selection_changed |= ImGui::Checkbox("Modifiers", &this->apply_modifiers);
|
||||||
|
ImGui::SameLine();
|
||||||
selection_changed |= ImGui::Checkbox("Keypads", &this->apply_keypads);
|
selection_changed |= ImGui::Checkbox("Keypads", &this->apply_keypads);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
selection_changed |= ImGui::Checkbox("Analogs", &this->apply_analogs);
|
selection_changed |= ImGui::Checkbox("Analogs", &this->apply_analogs);
|
||||||
@@ -5924,7 +6060,7 @@ namespace overlay::windows {
|
|||||||
std::fill(this->template_is_applied.begin(), this->template_is_applied.end(), false);
|
std::fill(this->template_is_applied.begin(), this->template_is_applied.end(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->apply_buttons && !this->apply_keypads &&
|
if (!this->apply_buttons && !this->apply_modifiers && !this->apply_keypads &&
|
||||||
!this->apply_analogs && !this->apply_lights) {
|
!this->apply_analogs && !this->apply_lights) {
|
||||||
ImGui::TextUnformatted("\nYou must select at least one group to apply.\n\n");
|
ImGui::TextUnformatted("\nYou must select at least one group to apply.\n\n");
|
||||||
}
|
}
|
||||||
@@ -5964,7 +6100,8 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!apply_buttons && !apply_keypads && !apply_analogs && !apply_lights) {
|
if (!apply_buttons && !apply_modifiers && !apply_keypads &&
|
||||||
|
!apply_analogs && !apply_lights) {
|
||||||
ImGui::BeginDisabled();
|
ImGui::BeginDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6133,7 +6270,8 @@ namespace overlay::windows {
|
|||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!apply_buttons && !apply_keypads && !apply_analogs && !apply_lights) {
|
if (!apply_buttons && !apply_modifiers && !apply_keypads &&
|
||||||
|
!apply_analogs && !apply_lights) {
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6212,12 +6350,14 @@ namespace overlay::windows {
|
|||||||
ImGui::TextUnformatted("Pick which groups to save:");
|
ImGui::TextUnformatted("Pick which groups to save:");
|
||||||
ImGui::Checkbox("Buttons", &this->save_buttons);
|
ImGui::Checkbox("Buttons", &this->save_buttons);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Modifiers", &this->save_modifiers);
|
||||||
|
ImGui::SameLine();
|
||||||
ImGui::Checkbox("Keypads", &this->save_keypads);
|
ImGui::Checkbox("Keypads", &this->save_keypads);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Checkbox("Analogs", &this->save_analogs);
|
ImGui::Checkbox("Analogs", &this->save_analogs);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Checkbox("Lights", &this->save_lights);
|
ImGui::Checkbox("Lights", &this->save_lights);
|
||||||
if (!this->save_analogs && !this->save_keypads &&
|
if (!this->save_buttons && !this->save_modifiers && !this->save_keypads &&
|
||||||
!this->save_analogs && !this->save_lights) {
|
!this->save_analogs && !this->save_lights) {
|
||||||
ImGui::TextUnformatted("\nYou must select at least one group to save.\n\n");
|
ImGui::TextUnformatted("\nYou must select at least one group to save.\n\n");
|
||||||
}
|
}
|
||||||
@@ -6269,7 +6409,8 @@ namespace overlay::windows {
|
|||||||
|
|
||||||
ImGui::BeginDisabled(
|
ImGui::BeginDisabled(
|
||||||
!all_labels_set ||
|
!all_labels_set ||
|
||||||
(!this->save_buttons && !this->save_keypads && !this->save_analogs && !this->save_lights));
|
(!this->save_buttons && !this->save_modifiers && !this->save_keypads &&
|
||||||
|
!this->save_analogs && !this->save_lights));
|
||||||
|
|
||||||
if (ImGui::Button("Save")) {
|
if (ImGui::Button("Save")) {
|
||||||
// replace device IDs with labels in the template
|
// replace device IDs with labels in the template
|
||||||
@@ -6278,7 +6419,8 @@ namespace overlay::windows {
|
|||||||
template_save_sources[si], template_save_labels[si]);
|
template_save_sources[si], template_save_labels[si]);
|
||||||
}
|
}
|
||||||
if (save_user_template(template_pending_save,
|
if (save_user_template(template_pending_save,
|
||||||
this->save_buttons, this->save_keypads, this->save_analogs, this->save_lights)) {
|
this->save_buttons, this->save_modifiers, this->save_keypads,
|
||||||
|
this->save_analogs, this->save_lights)) {
|
||||||
template_save_name[0] = '\0';
|
template_save_name[0] = '\0';
|
||||||
templates_cache_dirty = true;
|
templates_cache_dirty = true;
|
||||||
}
|
}
|
||||||
@@ -6360,7 +6502,7 @@ namespace overlay::windows {
|
|||||||
t.rename_source(template_save_sources[si], template_save_labels[si]);
|
t.rename_source(template_save_sources[si], template_save_labels[si]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
save_user_template(t, true, true, true, true);
|
save_user_template(t, true, true, true, true, true);
|
||||||
templates_cache_dirty = true;
|
templates_cache_dirty = true;
|
||||||
template_save_sources.clear();
|
template_save_sources.clear();
|
||||||
template_save_labels.clear();
|
template_save_labels.clear();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ namespace overlay::windows {
|
|||||||
CONTROLLER_PAGE_INVALID,
|
CONTROLLER_PAGE_INVALID,
|
||||||
CONTROLLER_PAGE_BUTTONS,
|
CONTROLLER_PAGE_BUTTONS,
|
||||||
CONTROLLER_PAGE_KEYPADS,
|
CONTROLLER_PAGE_KEYPADS,
|
||||||
|
CONTROLLER_PAGE_MODIFIERS,
|
||||||
CONTROLLER_PAGE_ANALOGS,
|
CONTROLLER_PAGE_ANALOGS,
|
||||||
CONTROLLER_PAGE_OVERLAY,
|
CONTROLLER_PAGE_OVERLAY,
|
||||||
CONTROLLER_PAGE_LIGHTS,
|
CONTROLLER_PAGE_LIGHTS,
|
||||||
@@ -119,10 +120,12 @@ namespace overlay::windows {
|
|||||||
std::vector<std::string> template_save_sources;
|
std::vector<std::string> template_save_sources;
|
||||||
std::vector<std::string> template_save_labels;
|
std::vector<std::string> template_save_labels;
|
||||||
bool apply_buttons = true;
|
bool apply_buttons = true;
|
||||||
|
bool apply_modifiers = true;
|
||||||
bool apply_keypads = true;
|
bool apply_keypads = true;
|
||||||
bool apply_analogs = true;
|
bool apply_analogs = true;
|
||||||
bool apply_lights = true;
|
bool apply_lights = true;
|
||||||
bool save_buttons = true;
|
bool save_buttons = true;
|
||||||
|
bool save_modifiers = true;
|
||||||
bool save_keypads = true;
|
bool save_keypads = true;
|
||||||
bool save_analogs = true;
|
bool save_analogs = true;
|
||||||
bool save_lights = true;
|
bool save_lights = true;
|
||||||
@@ -168,12 +171,14 @@ namespace overlay::windows {
|
|||||||
|
|
||||||
void bind_button_popup(const std::string &bind_name, Button *button, const int button_it_max, const int alt_index);
|
void bind_button_popup(const std::string &bind_name, Button *button, const int button_it_max, const int alt_index);
|
||||||
void naive_button_popup(const std::string &naive_string, Button *button, const int button_it_max, const int alt_index);
|
void naive_button_popup(const std::string &naive_string, Button *button, const int button_it_max, const int alt_index);
|
||||||
|
bool build_modifier_picker(Button &button);
|
||||||
void edit_button_popup(
|
void edit_button_popup(
|
||||||
const std::string &edit_name,
|
const std::string &edit_name,
|
||||||
const std::string &button_display,
|
const std::string &button_display,
|
||||||
Button *button,
|
Button *button,
|
||||||
const float button_velocity,
|
const float button_velocity,
|
||||||
const int alt_index);
|
const int alt_index,
|
||||||
|
const bool allow_modifiers);
|
||||||
void clear_button(Button *button, const int alt_index, std::optional<unsigned short> vKey_default = std::nullopt);
|
void clear_button(Button *button, const int alt_index, std::optional<unsigned short> vKey_default = std::nullopt);
|
||||||
void reset_button_to_default(Button *button, unsigned short vKey_default);
|
void reset_button_to_default(Button *button, unsigned short vKey_default);
|
||||||
unsigned int get_keypad_top_row(const Button &button);
|
unsigned int get_keypad_top_row(const Button &button);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
namespace overlay::windows {
|
namespace overlay::windows {
|
||||||
|
|
||||||
// helpers - iterate both buttons and keypad_buttons
|
// helpers - iterate all button groups
|
||||||
static void count_buttons_in(const std::vector<TemplateButtonBinding> &btns, int &naive, int &device) {
|
static void count_buttons_in(const std::vector<TemplateButtonBinding> &btns, int &naive, int &device) {
|
||||||
for (auto &btn : btns) {
|
for (auto &btn : btns) {
|
||||||
if (btn.primary.is_naive()) naive++;
|
if (btn.primary.is_naive()) naive++;
|
||||||
@@ -21,6 +21,7 @@ namespace overlay::windows {
|
|||||||
int ControllerTemplate::count_naive_buttons() const {
|
int ControllerTemplate::count_naive_buttons() const {
|
||||||
int naive = 0, device = 0;
|
int naive = 0, device = 0;
|
||||||
count_buttons_in(buttons, naive, device);
|
count_buttons_in(buttons, naive, device);
|
||||||
|
count_buttons_in(modifier_buttons, naive, device);
|
||||||
count_buttons_in(keypad_buttons, naive, device);
|
count_buttons_in(keypad_buttons, naive, device);
|
||||||
return naive;
|
return naive;
|
||||||
}
|
}
|
||||||
@@ -28,6 +29,7 @@ namespace overlay::windows {
|
|||||||
int ControllerTemplate::count_device_buttons() const {
|
int ControllerTemplate::count_device_buttons() const {
|
||||||
int naive = 0, device = 0;
|
int naive = 0, device = 0;
|
||||||
count_buttons_in(buttons, naive, device);
|
count_buttons_in(buttons, naive, device);
|
||||||
|
count_buttons_in(modifier_buttons, naive, device);
|
||||||
count_buttons_in(keypad_buttons, naive, device);
|
count_buttons_in(keypad_buttons, naive, device);
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
@@ -64,6 +66,7 @@ namespace overlay::windows {
|
|||||||
std::set<std::string> ControllerTemplate::get_used_devices() const {
|
std::set<std::string> ControllerTemplate::get_used_devices() const {
|
||||||
std::set<std::string> devices;
|
std::set<std::string> devices;
|
||||||
collect_devices_from(buttons, devices);
|
collect_devices_from(buttons, devices);
|
||||||
|
collect_devices_from(modifier_buttons, devices);
|
||||||
collect_devices_from(keypad_buttons, devices);
|
collect_devices_from(keypad_buttons, devices);
|
||||||
for (auto &a : analogs) {
|
for (auto &a : analogs) {
|
||||||
if (a.is_device()) devices.insert(a.device_identifier);
|
if (a.is_device()) devices.insert(a.device_identifier);
|
||||||
@@ -94,6 +97,7 @@ namespace overlay::windows {
|
|||||||
bool has_naive = false;
|
bool has_naive = false;
|
||||||
|
|
||||||
collect_sources_from(buttons, sources_set, has_naive);
|
collect_sources_from(buttons, sources_set, has_naive);
|
||||||
|
collect_sources_from(modifier_buttons, sources_set, has_naive);
|
||||||
collect_sources_from(keypad_buttons, sources_set, has_naive);
|
collect_sources_from(keypad_buttons, sources_set, has_naive);
|
||||||
for (auto &a : analogs) {
|
for (auto &a : analogs) {
|
||||||
if (a.is_device()) {
|
if (a.is_device()) {
|
||||||
@@ -131,6 +135,7 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
rename_in_buttons(buttons);
|
rename_in_buttons(buttons);
|
||||||
|
rename_in_buttons(modifier_buttons);
|
||||||
rename_in_buttons(keypad_buttons);
|
rename_in_buttons(keypad_buttons);
|
||||||
for (auto &a : analogs) {
|
for (auto &a : analogs) {
|
||||||
if (a.device_identifier == old_id) a.device_identifier = new_id;
|
if (a.device_identifier == old_id) a.device_identifier = new_id;
|
||||||
@@ -181,6 +186,16 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// modifier buttons
|
||||||
|
std::vector<std::string> modifier_lines;
|
||||||
|
collect_btn_lines(modifier_buttons, modifier_lines);
|
||||||
|
if (!modifier_lines.empty()) {
|
||||||
|
result += "Modifiers:\n";
|
||||||
|
for (auto &line : modifier_lines) {
|
||||||
|
result += " " + line + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// keypad buttons
|
// keypad buttons
|
||||||
std::vector<std::string> kp_lines;
|
std::vector<std::string> kp_lines;
|
||||||
collect_btn_lines(keypad_buttons, kp_lines);
|
collect_btn_lines(keypad_buttons, kp_lines);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace overlay::windows {
|
|||||||
double debounce_down = 0.0;
|
double debounce_down = 0.0;
|
||||||
int bat_threshold = 0;
|
int bat_threshold = 0;
|
||||||
unsigned short velocity_threshold = 0;
|
unsigned short velocity_threshold = 0;
|
||||||
|
uint8_t modifier_mask = 0;
|
||||||
|
|
||||||
bool is_naive() const { return device_identifier.empty() && vKey != INVALID_VKEY; }
|
bool is_naive() const { return device_identifier.empty() && vKey != INVALID_VKEY; }
|
||||||
bool is_device() const { return !device_identifier.empty(); }
|
bool is_device() const { return !device_identifier.empty(); }
|
||||||
@@ -34,6 +35,7 @@ namespace overlay::windows {
|
|||||||
e.debounce_down = btn.getDebounceDown();
|
e.debounce_down = btn.getDebounceDown();
|
||||||
e.bat_threshold = btn.getBatThreshold();
|
e.bat_threshold = btn.getBatThreshold();
|
||||||
e.velocity_threshold = btn.getVelocityThreshold();
|
e.velocity_threshold = btn.getVelocityThreshold();
|
||||||
|
e.modifier_mask = btn.getModifierMask();
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -123,6 +125,7 @@ namespace overlay::windows {
|
|||||||
std::string game_name;
|
std::string game_name;
|
||||||
bool is_builtin = false;
|
bool is_builtin = false;
|
||||||
std::vector<TemplateButtonBinding> buttons;
|
std::vector<TemplateButtonBinding> buttons;
|
||||||
|
std::vector<TemplateButtonBinding> modifier_buttons;
|
||||||
std::vector<TemplateButtonBinding> keypad_buttons;
|
std::vector<TemplateButtonBinding> keypad_buttons;
|
||||||
std::vector<TemplateAnalogBinding> analogs;
|
std::vector<TemplateAnalogBinding> analogs;
|
||||||
std::vector<TemplateLightBinding> lights;
|
std::vector<TemplateLightBinding> lights;
|
||||||
@@ -150,6 +153,7 @@ namespace overlay::windows {
|
|||||||
bool save_user_template(
|
bool save_user_template(
|
||||||
const ControllerTemplate &tmpl,
|
const ControllerTemplate &tmpl,
|
||||||
const bool save_buttons,
|
const bool save_buttons,
|
||||||
|
const bool save_modifiers,
|
||||||
const bool save_keypads,
|
const bool save_keypads,
|
||||||
const bool save_analogs,
|
const bool save_analogs,
|
||||||
const bool save_lights);
|
const bool save_lights);
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ namespace overlay::windows {
|
|||||||
this->texture_width = 0;
|
this->texture_width = 0;
|
||||||
this->texture_height = 0;
|
this->texture_height = 0;
|
||||||
|
|
||||||
overlay->set_subscreen_mouse_handler([this](LONG *x, LONG *y) -> bool {
|
overlay->set_subscreen_touch_transform([this](LONG *x, LONG *y) -> bool {
|
||||||
// convert to normalized form (relative window coordinates 0.f-1.f)
|
// convert to normalized form (relative window coordinates 0.f-1.f)
|
||||||
ImVec2 xy;
|
ImVec2 xy;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
#include "nostalgia_touch_piano.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "external/imgui/imgui_internal.h"
|
||||||
|
#include "games/nost/touch_mode.h"
|
||||||
|
|
||||||
|
namespace overlay::windows {
|
||||||
|
|
||||||
|
static constexpr float BUTTON_WIDTH = 144.f;
|
||||||
|
static constexpr float BUTTON_HEIGHT = 40.f;
|
||||||
|
static constexpr float WINDOW_PADDING = 4.f;
|
||||||
|
static constexpr float EDGE_MARGIN = 4.f;
|
||||||
|
static constexpr float PIANO_HEIGHT_RATIO = 0.08f;
|
||||||
|
static constexpr float PIANO_LEFT_GAP = 11.f;
|
||||||
|
static constexpr float PIANO_RIGHT_GAP = 10.f;
|
||||||
|
static constexpr uint32_t PIANO_KEY_COUNT = 28;
|
||||||
|
|
||||||
|
static constexpr ImU32 PIANO_KEY_COLOR = IM_COL32(255, 255, 255, 50);
|
||||||
|
static constexpr ImU32 PIANO_KEY_ACTIVE_COLOR = IM_COL32(255, 48, 48, 160);
|
||||||
|
static constexpr ImU32 PIANO_KEY_BORDER_COLOR = IM_COL32(0, 0, 0, 100);
|
||||||
|
|
||||||
|
struct ButtonPalette {
|
||||||
|
ImVec4 normal;
|
||||||
|
ImVec4 hovered;
|
||||||
|
ImVec4 active;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const ButtonPalette NAV_MODE_PALETTE {
|
||||||
|
ImVec4(0.10f, 0.45f, 0.28f, 0.72f),
|
||||||
|
ImVec4(0.14f, 0.58f, 0.36f, 0.82f),
|
||||||
|
ImVec4(0.08f, 0.34f, 0.21f, 0.90f),
|
||||||
|
};
|
||||||
|
static const ButtonPalette PIANO_MODE_PALETTE {
|
||||||
|
ImVec4(0.15f, 0.32f, 0.62f, 0.72f),
|
||||||
|
ImVec4(0.20f, 0.42f, 0.78f, 0.82f),
|
||||||
|
ImVec4(0.10f, 0.24f, 0.50f, 0.90f),
|
||||||
|
};
|
||||||
|
|
||||||
|
static void draw_piano_keys(
|
||||||
|
const ImVec2 &display_size,
|
||||||
|
LONG client_width,
|
||||||
|
uint32_t key_state) {
|
||||||
|
|
||||||
|
if (display_size.x <= 0.f || display_size.y <= 0.f || client_width <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is only a visual guide; native touch routing owns the actual input
|
||||||
|
const float left_gap = PIANO_LEFT_GAP * display_size.x / client_width;
|
||||||
|
const float right_gap = PIANO_RIGHT_GAP * display_size.x / client_width;
|
||||||
|
const float piano_width = display_size.x - left_gap - right_gap;
|
||||||
|
if (piano_width <= 0.f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float key_width = piano_width / PIANO_KEY_COUNT;
|
||||||
|
const float key_top = display_size.y * (1.f - PIANO_HEIGHT_RATIO);
|
||||||
|
auto *draw_list = ImGui::GetBackgroundDrawList();
|
||||||
|
|
||||||
|
for (uint32_t key = 0; key < PIANO_KEY_COUNT; key++) {
|
||||||
|
const ImVec2 key_min(left_gap + key * key_width, key_top);
|
||||||
|
const ImVec2 key_max(left_gap + (key + 1) * key_width, display_size.y);
|
||||||
|
const bool active = (key_state & (UINT32_C(1) << key)) != 0;
|
||||||
|
draw_list->AddRectFilled(
|
||||||
|
key_min,
|
||||||
|
key_max,
|
||||||
|
active ? PIANO_KEY_ACTIVE_COLOR : PIANO_KEY_COLOR);
|
||||||
|
draw_list->AddRect(key_min, key_max, PIANO_KEY_BORDER_COLOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NostalgiaTouchPiano::NostalgiaTouchPiano(SpiceOverlay *overlay) : Window(overlay) {
|
||||||
|
this->title = "Nostalgia Touch Piano";
|
||||||
|
this->flags = ImGuiWindowFlags_NoTitleBar
|
||||||
|
| ImGuiWindowFlags_NoResize
|
||||||
|
| ImGuiWindowFlags_NoCollapse
|
||||||
|
| ImGuiWindowFlags_NoMove
|
||||||
|
| ImGuiWindowFlags_NoDocking
|
||||||
|
| ImGuiWindowFlags_NoBackground
|
||||||
|
| ImGuiWindowFlags_NoSavedSettings
|
||||||
|
| ImGuiWindowFlags_NoNav
|
||||||
|
| ImGuiWindowFlags_NoBringToFrontOnFocus;
|
||||||
|
this->window_padding = overlay::apply_scaling_to_vector(WINDOW_PADDING, WINDOW_PADDING);
|
||||||
|
this->set_active(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NostalgiaTouchPiano::calculate_initial_window() {
|
||||||
|
this->init_size = overlay::apply_scaling_to_vector(
|
||||||
|
BUTTON_WIDTH + WINDOW_PADDING * 2,
|
||||||
|
BUTTON_HEIGHT + WINDOW_PADDING * 2);
|
||||||
|
this->init_pos = overlay::apply_scaling_to_vector(EDGE_MARGIN, EDGE_MARGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NostalgiaTouchPiano::build_content() {
|
||||||
|
// keep the control anchored while the game window changes size or mode
|
||||||
|
ImGui::SetWindowPos(
|
||||||
|
overlay::apply_scaling_to_vector(EDGE_MARGIN, EDGE_MARGIN),
|
||||||
|
ImGuiCond_Always);
|
||||||
|
|
||||||
|
// stay above regular overlay windows, but never cover a blocking modal
|
||||||
|
ImGuiWindow *mode_window = ImGui::GetCurrentWindow();
|
||||||
|
if (ImGuiWindow *modal = ImGui::GetTopMostPopupModal()) {
|
||||||
|
ImGui::BringWindowToDisplayBehind(mode_window, modal);
|
||||||
|
} else {
|
||||||
|
ImGui::BringWindowToDisplayFront(mode_window);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool nav_mode =
|
||||||
|
games::nost::touch_mode::current_mode() == games::nost::touch_mode::Mode::Nav;
|
||||||
|
const char *label = nav_mode ? "Nav Mode" : "Piano Mode";
|
||||||
|
|
||||||
|
// make the active routing mode recognizable without reading the label
|
||||||
|
const auto &palette = nav_mode ? NAV_MODE_PALETTE : PIANO_MODE_PALETTE;
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, palette.normal);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, palette.hovered);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, palette.active);
|
||||||
|
ImGui::Button(label, overlay::apply_scaling_to_vector(BUTTON_WIDTH, BUTTON_HEIGHT));
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
|
||||||
|
const auto &io = ImGui::GetIO();
|
||||||
|
RECT client_rect {};
|
||||||
|
if (io.DisplaySize.x > 0.f && io.DisplaySize.y > 0.f &&
|
||||||
|
GetClientRect(this->overlay->get_window(), &client_rect)) {
|
||||||
|
|
||||||
|
// convert the rendered imgui rectangle into the client coordinates
|
||||||
|
// used by hardware touch publication and piano-key mapping
|
||||||
|
const auto item_min = ImGui::GetItemRectMin();
|
||||||
|
const auto item_max = ImGui::GetItemRectMax();
|
||||||
|
const auto client_width = client_rect.right - client_rect.left;
|
||||||
|
const auto client_height = client_rect.bottom - client_rect.top;
|
||||||
|
if (!nav_mode) {
|
||||||
|
draw_piano_keys(
|
||||||
|
io.DisplaySize,
|
||||||
|
client_width,
|
||||||
|
games::nost::touch_mode::piano_key_state());
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT button_bounds {
|
||||||
|
static_cast<LONG>(std::lround(item_min.x * client_width / io.DisplaySize.x)),
|
||||||
|
static_cast<LONG>(std::lround(item_min.y * client_height / io.DisplaySize.y)),
|
||||||
|
static_cast<LONG>(std::lround(item_max.x * client_width / io.DisplaySize.x)),
|
||||||
|
static_cast<LONG>(std::lround(item_max.y * client_height / io.DisplaySize.y)),
|
||||||
|
};
|
||||||
|
games::nost::touch_mode::publish_button_bounds(
|
||||||
|
this->overlay->get_window(), button_bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "overlay/window.h"
|
||||||
|
|
||||||
|
namespace overlay::windows {
|
||||||
|
|
||||||
|
// persistent mode control rendered independently of the main overlay visibility
|
||||||
|
class NostalgiaTouchPiano : public Window {
|
||||||
|
public:
|
||||||
|
explicit NostalgiaTouchPiano(SpiceOverlay *overlay);
|
||||||
|
|
||||||
|
void calculate_initial_window() override;
|
||||||
|
void build_content() override;
|
||||||
|
};
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,183 +1,21 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "overlay/window.h"
|
#include "overlay/window.h"
|
||||||
#include <map>
|
#include "patcher/patch_manager.h"
|
||||||
#include <functional>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <optional>
|
|
||||||
#include "external/rapidjson/document.h"
|
|
||||||
|
|
||||||
namespace overlay::windows {
|
namespace overlay::windows {
|
||||||
|
|
||||||
enum class PatchType {
|
|
||||||
Unknown,
|
|
||||||
Memory,
|
|
||||||
Signature,
|
|
||||||
Union,
|
|
||||||
Integer,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class PatchStatus {
|
|
||||||
Error,
|
|
||||||
Disabled,
|
|
||||||
Enabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class PatchUrlStatus {
|
|
||||||
Valid,
|
|
||||||
Invalid,
|
|
||||||
Unapplied,
|
|
||||||
ValidButNoData,
|
|
||||||
Partial,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MemoryPatch {
|
|
||||||
std::string dll_name = "";
|
|
||||||
std::shared_ptr<uint8_t[]> data_disabled = nullptr;
|
|
||||||
size_t data_disabled_len = 0;
|
|
||||||
std::shared_ptr<uint8_t[]> data_enabled = nullptr;
|
|
||||||
size_t data_enabled_len = 0;
|
|
||||||
uint64_t data_offset = 0;
|
|
||||||
uint8_t *data_offset_ptr = nullptr;
|
|
||||||
bool fatal_error = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PatchData;
|
|
||||||
struct SignaturePatch {
|
|
||||||
std::string dll_name = "";
|
|
||||||
std::string signature = "", replacement = "";
|
|
||||||
uint64_t offset = 0;
|
|
||||||
int64_t usage = 0;
|
|
||||||
|
|
||||||
MemoryPatch to_memory(PatchData *patch);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct UnionPatch {
|
|
||||||
std::string name = "";
|
|
||||||
std::string dll_name = "";
|
|
||||||
std::shared_ptr<uint8_t[]> data = nullptr;
|
|
||||||
size_t data_len = 0;
|
|
||||||
uint64_t offset = 0;
|
|
||||||
uint8_t* data_offset_ptr = nullptr;
|
|
||||||
bool fatal_error = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct NumberPatch {
|
|
||||||
std::string dll_name = "";
|
|
||||||
uint64_t data_offset = 0;
|
|
||||||
uint8_t* data_offset_ptr = nullptr;
|
|
||||||
int32_t min;
|
|
||||||
int32_t max;
|
|
||||||
int32_t value;
|
|
||||||
size_t size_in_bytes;
|
|
||||||
bool fatal_error = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PatchData {
|
|
||||||
bool enabled;
|
|
||||||
std::string game_code;
|
|
||||||
int datecode_min = 0;
|
|
||||||
int datecode_max = 0;
|
|
||||||
std::string name, description, caution;
|
|
||||||
std::string name_in_lower_case = "";
|
|
||||||
PatchType type;
|
|
||||||
bool preset;
|
|
||||||
std::vector<MemoryPatch> patches_memory;
|
|
||||||
std::vector<UnionPatch> patches_union;
|
|
||||||
NumberPatch patch_number;
|
|
||||||
PatchStatus last_status;
|
|
||||||
std::string hash;
|
|
||||||
bool unverified = false;
|
|
||||||
std::string peIdentifier;
|
|
||||||
std::string error_reason = "";
|
|
||||||
|
|
||||||
// for union patch only
|
|
||||||
std::string selected_union_name = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
extern std::optional<std::string> PATCH_MANAGER_CFG_PATH_OVERRIDE;
|
|
||||||
|
|
||||||
std::string get_game_identifier(const std::filesystem::path& dll_path, bool print_info=false);
|
|
||||||
|
|
||||||
class PatchManager : public Window {
|
class PatchManager : public Window {
|
||||||
public:
|
public:
|
||||||
|
explicit PatchManager(SpiceOverlay *overlay);
|
||||||
PatchManager(SpiceOverlay *overlay, bool apply_patches = false);
|
|
||||||
~PatchManager() override;
|
~PatchManager() override;
|
||||||
|
|
||||||
void build_content() override;
|
void build_content() override;
|
||||||
void reload_local_patches(bool apply_patches = false);
|
|
||||||
bool import_remote_patches_to_disk();
|
|
||||||
bool load_from_patches_json(bool apply_patches);
|
|
||||||
bool import_remote_patches_for_dll(const std::string& url, const std::string& dll_name);
|
|
||||||
void hard_apply_patches();
|
|
||||||
void load_embedded_patches(bool apply_patches);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
// configuration
|
|
||||||
static std::filesystem::path config_path;
|
|
||||||
static bool config_dirty;
|
|
||||||
static bool setting_auto_apply;
|
|
||||||
static std::vector<std::string> setting_auto_apply_list;
|
|
||||||
static std::vector<std::string> setting_patches_enabled;
|
|
||||||
static std::map<std::string, std::string> setting_union_patches_enabled;
|
|
||||||
static std::map<std::string, int64_t> setting_int_patches_enabled;
|
|
||||||
static std::string patch_url;
|
|
||||||
static std::string patch_name_filter;
|
static std::string patch_name_filter;
|
||||||
|
|
||||||
static std::filesystem::path LOCAL_PATCHES_PATH;
|
|
||||||
static std::string ACTIVE_JSON_FILE;
|
|
||||||
|
|
||||||
// patches
|
|
||||||
static std::vector<PatchData> patches;
|
|
||||||
static bool local_patches_initialized;
|
|
||||||
|
|
||||||
// cached sorted view of `patches` for the table, stored as indices so a
|
|
||||||
// stale entry can never dangle if `patches` is rebuilt; rebuilt only when
|
|
||||||
// the sort order changes or the patch list is reloaded (not every frame)
|
|
||||||
static std::vector<size_t> patches_sorted;
|
|
||||||
|
|
||||||
void config_load();
|
|
||||||
void config_save();
|
|
||||||
|
|
||||||
// rebuild the cached sorted view of `patches` from the active table's
|
|
||||||
// sort specs; no-op unless the sort changed or the patch list changed
|
|
||||||
void update_sorted_patches();
|
void update_sorted_patches();
|
||||||
|
void show_patch_tooltip(const patcher::PatchData& patch);
|
||||||
void append_patches(
|
|
||||||
std::string &patches_json,
|
|
||||||
bool apply_patches = false,
|
|
||||||
std::function<bool(const PatchData&)> filter = std::function<bool(const PatchData&)>(),
|
|
||||||
std::string pe_identifier_for_patch = "");
|
|
||||||
|
|
||||||
void show_patch_tooltip(const PatchData& patch);
|
|
||||||
|
|
||||||
bool is_game_id_wildcard_matched(const std::string& id_from_config);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
PatchStatus is_patch_active(PatchData &patch);
|
|
||||||
bool apply_patch(PatchData &patch, bool active);
|
|
||||||
|
|
||||||
int64_t parse_little_endian_int(uint8_t* bytes, size_t size);
|
|
||||||
void int_to_little_endian_bytes(int64_t value, uint8_t* bytes, size_t size);
|
|
||||||
|
|
||||||
std::vector<uint8_t>* find_in_dll_map(
|
|
||||||
const std::string& dll_name, size_t offset, size_t size);
|
|
||||||
std::vector<uint8_t>* find_in_dll_map_org(
|
|
||||||
const std::string& dll_name, size_t offset, size_t size);
|
|
||||||
|
|
||||||
bool restore_bytes_from_dll_map_org(
|
|
||||||
uint8_t* destination, const std::string& dll_name, size_t offset, size_t size);
|
|
||||||
|
|
||||||
void create_dll_backup(
|
|
||||||
std::vector<std::filesystem::path>& written_list, const std::filesystem::path& dll_path);
|
|
||||||
std::string fix_up_dll_name(const std::string& dll_name);
|
|
||||||
uint8_t* get_dll_offset_for_patch_apply(
|
|
||||||
const std::string& dll_name, const uint64_t data_offset, const size_t size_in_bytes);
|
|
||||||
|
|
||||||
uint64_t parse_json_data_offset(
|
|
||||||
const std::string &patch_name, const rapidjson::Value &value);
|
|
||||||
|
|
||||||
void print_auto_apply_status(PatchData &patch);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include "avs/game.h"
|
||||||
|
#include "cfg/configurator.h"
|
||||||
|
#include "external/hash-library/sha256.h"
|
||||||
|
#include "external/rapidjson/document.h"
|
||||||
|
#include "external/rapidjson/prettywriter.h"
|
||||||
|
#include "external/rapidjson/stringbuffer.h"
|
||||||
|
#include "util/fileutils.h"
|
||||||
|
#include "util/logging.h"
|
||||||
|
|
||||||
|
using namespace rapidjson;
|
||||||
|
|
||||||
|
namespace patcher {
|
||||||
|
|
||||||
|
bool is_game_id_wildcard_matched(const std::string& id_from_config) {
|
||||||
|
return ((id_from_config.compare(0, 3, avs::game::MODEL) == 0) &&
|
||||||
|
(id_from_config.compare(10, 10, avs::game::EXT) == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void config_load() {
|
||||||
|
log_info("patchmanager", "loading config");
|
||||||
|
|
||||||
|
// read config file
|
||||||
|
std::string config = fileutils::text_read(config_path);
|
||||||
|
if (!config.empty()) {
|
||||||
|
|
||||||
|
// parse document
|
||||||
|
Document doc;
|
||||||
|
doc.Parse(config.c_str());
|
||||||
|
|
||||||
|
// check parse error
|
||||||
|
auto error = doc.GetParseError();
|
||||||
|
if (error) {
|
||||||
|
log_warning("patchmanager", "config file parse error: {}", static_cast<uint32_t>(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify root is a dict
|
||||||
|
if (doc.IsObject()) {
|
||||||
|
|
||||||
|
// read auto apply settings
|
||||||
|
auto auto_apply = doc.FindMember("auto_apply");
|
||||||
|
if (auto_apply != doc.MemberEnd() && auto_apply->value.IsArray()) {
|
||||||
|
|
||||||
|
// get game id
|
||||||
|
const auto game_id = avs::game::get_identifier();
|
||||||
|
|
||||||
|
// iterate entries
|
||||||
|
setting_auto_apply = false;
|
||||||
|
setting_auto_apply_list.clear();
|
||||||
|
for (auto &entry : auto_apply->value.GetArray()) {
|
||||||
|
if (entry.IsString()) {
|
||||||
|
|
||||||
|
// check if this is our game identifier
|
||||||
|
const std::string entry_id = entry.GetString();
|
||||||
|
|
||||||
|
if (!setting_auto_apply) {
|
||||||
|
if (game_id == entry_id) {
|
||||||
|
// exact match
|
||||||
|
setting_auto_apply = true;
|
||||||
|
log_misc(
|
||||||
|
"patchmanager",
|
||||||
|
"matched auto apply entry by full game identifier: {}",
|
||||||
|
entry_id);
|
||||||
|
|
||||||
|
} else if (is_game_id_wildcard_matched(entry_id)) {
|
||||||
|
// match on model and ext, ignoring dest/spec/rev
|
||||||
|
// sample: LDJ:J:E:A:2025011400
|
||||||
|
setting_auto_apply = true;
|
||||||
|
log_misc(
|
||||||
|
"patchmanager",
|
||||||
|
"matched auto apply entry by partial game identifier: {}:?:?:?:{}",
|
||||||
|
avs::game::MODEL, avs::game::EXT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move to list
|
||||||
|
setting_auto_apply_list.emplace_back(entry_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!setting_auto_apply) {
|
||||||
|
log_misc(
|
||||||
|
"patchmanager",
|
||||||
|
"no auto apply entry matched, patches will not load automatically");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read enabled patches
|
||||||
|
auto patches_enabled = doc.FindMember("patches_enabled");
|
||||||
|
if (patches_enabled != doc.MemberEnd() && patches_enabled->value.IsArray()) {
|
||||||
|
setting_patches_enabled.clear();
|
||||||
|
for (const auto &patch : patches_enabled->value.GetArray()) {
|
||||||
|
if (patch.IsString()) {
|
||||||
|
setting_patches_enabled.emplace_back(std::string(patch.GetString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// read enabled union patches
|
||||||
|
auto patches_union_enabled = doc.FindMember("union_patches_enabled");
|
||||||
|
if (patches_union_enabled != doc.MemberEnd() && patches_union_enabled->value.IsObject()) {
|
||||||
|
setting_union_patches_enabled.clear();
|
||||||
|
for (auto it = patches_union_enabled->value.MemberBegin(); it != patches_union_enabled->value.MemberEnd(); ++it) {
|
||||||
|
if (it->name.IsString() && it->value.IsString()) {
|
||||||
|
setting_union_patches_enabled[it->name.GetString()] = it->value.GetString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// read enabled integer patches
|
||||||
|
auto patches_int_enabled = doc.FindMember("integer_patches_enabled");
|
||||||
|
if (patches_int_enabled != doc.MemberEnd() && patches_int_enabled->value.IsObject()) {
|
||||||
|
setting_int_patches_enabled.clear();
|
||||||
|
for (auto it = patches_int_enabled->value.MemberBegin(); it != patches_int_enabled->value.MemberEnd(); ++it) {
|
||||||
|
if (it->name.IsString() && it->value.IsNumber()) {
|
||||||
|
setting_int_patches_enabled[it->name.GetString()] = it->value.GetInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read remote patch URLs
|
||||||
|
auto remote_url_history = doc.FindMember("remote_url_history");
|
||||||
|
if (remote_url_history != doc.MemberEnd() && remote_url_history->value.IsArray()) {
|
||||||
|
url_recents.clear();
|
||||||
|
for (const auto &url : remote_url_history->value.GetArray()) {
|
||||||
|
if (url.IsString()) {
|
||||||
|
url_recents.emplace_back(std::string(url.GetString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string patch_hash(PatchData &patch) {
|
||||||
|
SHA256 hash;
|
||||||
|
hash.add(patch.game_code.c_str(), patch.game_code.length());
|
||||||
|
if (patch.datecode_min != 0 || patch.datecode_max != 0) {
|
||||||
|
hash.add(&patch.datecode_min, sizeof(patch.datecode_min));
|
||||||
|
hash.add(&patch.datecode_max, sizeof(patch.datecode_max));
|
||||||
|
}
|
||||||
|
if (!patch.peIdentifier.empty()) {
|
||||||
|
hash.add(patch.peIdentifier.c_str(), patch.peIdentifier.length());
|
||||||
|
}
|
||||||
|
hash.add(patch.name.c_str(), patch.name.length());
|
||||||
|
hash.add(patch.description.c_str(), patch.description.length());
|
||||||
|
return hash.getHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
void config_save() {
|
||||||
|
|
||||||
|
// create document
|
||||||
|
Document doc;
|
||||||
|
doc.Parse(
|
||||||
|
"{"
|
||||||
|
" \"auto_apply\": [],"
|
||||||
|
" \"patches_enabled\": [],"
|
||||||
|
" \"union_patches_enabled\": {},"
|
||||||
|
" \"integer_patches_enabled\": {},"
|
||||||
|
" \"remote_url_history\": []"
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
|
||||||
|
// check parse error
|
||||||
|
auto error = doc.GetParseError();
|
||||||
|
if (error) {
|
||||||
|
log_warning("patchmanager", "template parse error: {}", static_cast<uint32_t>(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
// auto apply setting
|
||||||
|
auto &auto_apply_list = doc["auto_apply"];
|
||||||
|
auto game_id = avs::game::get_identifier();
|
||||||
|
bool game_id_added = false;
|
||||||
|
for (auto &entry : setting_auto_apply_list) {
|
||||||
|
if (entry == game_id || is_game_id_wildcard_matched(entry)) {
|
||||||
|
if (!setting_auto_apply) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
game_id_added = true;
|
||||||
|
}
|
||||||
|
auto_apply_list.PushBack(StringRef(entry.c_str()), doc.GetAllocator());
|
||||||
|
}
|
||||||
|
if (setting_auto_apply && !game_id_added) {
|
||||||
|
auto_apply_list.PushBack(StringRef(game_id.c_str()), doc.GetAllocator());
|
||||||
|
}
|
||||||
|
|
||||||
|
// get enabled patches
|
||||||
|
auto &doc_patches_enabled = doc["patches_enabled"];
|
||||||
|
auto &doc_union_patches_enable = doc["union_patches_enabled"];
|
||||||
|
auto &doc_int_patches_enable = doc["integer_patches_enabled"];
|
||||||
|
for (auto &patch : patches) {
|
||||||
|
auto hash = patch_hash(patch);
|
||||||
|
|
||||||
|
if (patch.type == PatchType::Union) {
|
||||||
|
// enable hash if known as enabled, overridden and missing from list
|
||||||
|
if (patch.enabled) {
|
||||||
|
setting_union_patches_enabled[hash] = patch.selected_union_name;
|
||||||
|
} else {
|
||||||
|
setting_union_patches_enabled.erase(hash);
|
||||||
|
}
|
||||||
|
} else if (patch.type == PatchType::Integer) {
|
||||||
|
if (patch.enabled) {
|
||||||
|
setting_int_patches_enabled[hash] = patch.patch_number.value;
|
||||||
|
} else {
|
||||||
|
setting_int_patches_enabled.erase(hash);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// hash patch and find entry
|
||||||
|
auto entry = std::find(setting_patches_enabled.begin(), setting_patches_enabled.end(), hash);
|
||||||
|
|
||||||
|
// enable hash if known as enabled, overridden and missing from list
|
||||||
|
if ((patch.last_status == PatchStatus::Enabled && patch.enabled)
|
||||||
|
|| (cfg::CONFIGURATOR_STANDALONE && patch.last_status == PatchStatus::Error && patch.enabled)) {
|
||||||
|
if (entry == setting_patches_enabled.end()) {
|
||||||
|
setting_patches_enabled.emplace_back(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable hash if patch known as disabled
|
||||||
|
if (patch.last_status == PatchStatus::Disabled
|
||||||
|
|| (cfg::CONFIGURATOR_STANDALONE && patch.last_status == PatchStatus::Error && !patch.enabled)) {
|
||||||
|
if (entry != setting_patches_enabled.end()) {
|
||||||
|
setting_patches_enabled.erase(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add hashes to document
|
||||||
|
for (auto &hash : setting_patches_enabled) {
|
||||||
|
Value hash_value(hash.c_str(), doc.GetAllocator());
|
||||||
|
doc_patches_enabled.PushBack(hash_value, doc.GetAllocator());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& it : setting_union_patches_enabled) {
|
||||||
|
const std::string& key = it.first;
|
||||||
|
const std::string& val = it.second;
|
||||||
|
doc_union_patches_enable.AddMember(StringRef(key.c_str()), StringRef(val.c_str()), doc.GetAllocator());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& it : setting_int_patches_enabled) {
|
||||||
|
const std::string& key = it.first;
|
||||||
|
const int32_t& val = it.second;
|
||||||
|
doc_int_patches_enable.AddMember(StringRef(key.c_str()), val, doc.GetAllocator());
|
||||||
|
}
|
||||||
|
|
||||||
|
// remote URLs
|
||||||
|
auto &doc_url_history = doc["remote_url_history"];
|
||||||
|
for (auto& url : url_recents) {
|
||||||
|
Value url_value(url.c_str(), doc.GetAllocator());
|
||||||
|
doc_url_history.PushBack(url_value, doc.GetAllocator());
|
||||||
|
}
|
||||||
|
|
||||||
|
// build JSON
|
||||||
|
StringBuffer buffer;
|
||||||
|
PrettyWriter<StringBuffer> writer(buffer);
|
||||||
|
doc.Accept(writer);
|
||||||
|
|
||||||
|
// save to file
|
||||||
|
if (fileutils::write_config_file("patchmanager", config_path, buffer.GetString())) {
|
||||||
|
config_dirty = false;
|
||||||
|
} else {
|
||||||
|
log_warning("patchmanager", "unable to save config file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "patch_manager.h"
|
||||||
|
#include "external/rapidjson/document.h"
|
||||||
|
#include "util/nt_loader.h"
|
||||||
|
|
||||||
|
namespace patcher {
|
||||||
|
|
||||||
|
// internal engine state (patcher module only)
|
||||||
|
extern std::vector<std::string> setting_auto_apply_list;
|
||||||
|
extern std::vector<std::string> setting_patches_enabled;
|
||||||
|
extern std::map<std::string, std::string> setting_union_patches_enabled;
|
||||||
|
extern std::map<std::string, int64_t> setting_int_patches_enabled;
|
||||||
|
extern std::map<std::pair<std::string, std::string>, PatchGroup> patch_groups;
|
||||||
|
extern std::filesystem::path LOCAL_PATCHES_PATH;
|
||||||
|
extern std::map<std::string, std::vector<std::string>> EXTRA_DLLS;
|
||||||
|
extern bool ldr_registered;
|
||||||
|
extern void *ldr_notify_cookie;
|
||||||
|
extern std::vector<std::string> ldr_target_libraries;
|
||||||
|
|
||||||
|
// internal helpers
|
||||||
|
void append_patches(
|
||||||
|
std::string& patches_json,
|
||||||
|
bool apply_patches = false,
|
||||||
|
std::function<bool(const PatchData&)> filter = std::function<bool(const PatchData&)>(),
|
||||||
|
std::string pe_identifier_for_patch = "");
|
||||||
|
bool is_game_id_wildcard_matched(const std::string& id_from_config);
|
||||||
|
std::string getFromUrl(const std::string& dll_name, const std::string& url);
|
||||||
|
bool load_from_patches_json(bool apply_patches);
|
||||||
|
void load_embedded_patches(bool apply_patches);
|
||||||
|
bool import_remote_patches_for_dll(const std::string& url, const std::string& dll_name);
|
||||||
|
bool is_patch_group_definition(const rapidjson::Value& patch);
|
||||||
|
std::map<std::pair<std::string, std::string>, PatchGroup> parse_patch_group_definitions(
|
||||||
|
const rapidjson::Document& doc);
|
||||||
|
std::string resolve_patch_group_id(
|
||||||
|
const rapidjson::Value& patch,
|
||||||
|
const std::map<std::pair<std::string, std::string>, PatchGroup>& groups,
|
||||||
|
const std::string& game_code,
|
||||||
|
const char *patch_name);
|
||||||
|
void register_patch_group(
|
||||||
|
PatchData& patch,
|
||||||
|
const std::map<std::pair<std::string, std::string>, PatchGroup>& definitions);
|
||||||
|
VOID CALLBACK loader_notification(ULONG reason, PCLDR_DLL_NOTIFICATION_DATA data, PVOID context);
|
||||||
|
|
||||||
|
std::string patch_hash(PatchData& patch);
|
||||||
|
void clear_dll_maps();
|
||||||
|
|
||||||
|
int64_t parse_little_endian_int(uint8_t *bytes, size_t size);
|
||||||
|
void int_to_little_endian_bytes(int64_t value, uint8_t *bytes, size_t size);
|
||||||
|
|
||||||
|
std::vector<uint8_t> *find_in_dll_map(
|
||||||
|
const std::string& dll_name, size_t offset, size_t size);
|
||||||
|
std::vector<uint8_t> *find_in_dll_map_org(
|
||||||
|
const std::string& dll_name, size_t offset, size_t size);
|
||||||
|
|
||||||
|
bool restore_bytes_from_dll_map_org(
|
||||||
|
uint8_t *destination, const std::string& dll_name, size_t offset, size_t size);
|
||||||
|
|
||||||
|
void create_dll_backup(
|
||||||
|
std::vector<std::filesystem::path>& written_list, const std::filesystem::path& dll_path);
|
||||||
|
std::string fix_up_dll_name(const std::string& dll_name);
|
||||||
|
uint8_t *get_dll_offset_for_patch_apply(
|
||||||
|
const std::string& dll_name, uint64_t data_offset, size_t size_in_bytes);
|
||||||
|
|
||||||
|
uint64_t parse_json_data_offset(
|
||||||
|
const std::string& patch_name, const rapidjson::Value& value);
|
||||||
|
|
||||||
|
void print_auto_apply_status(PatchData& patch);
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "avs/game.h"
|
||||||
|
#include "cfg/configurator.h"
|
||||||
|
#include "util/fileutils.h"
|
||||||
|
#include "util/logging.h"
|
||||||
|
#include "util/utils.h"
|
||||||
|
|
||||||
|
namespace patcher {
|
||||||
|
|
||||||
|
std::optional<std::string> PATCH_MANAGER_CFG_PATH_OVERRIDE;
|
||||||
|
|
||||||
|
std::filesystem::path config_path;
|
||||||
|
bool config_dirty = false;
|
||||||
|
bool setting_auto_apply = false;
|
||||||
|
|
||||||
|
std::vector<std::string> setting_auto_apply_list;
|
||||||
|
std::vector<std::string> setting_patches_enabled;
|
||||||
|
|
||||||
|
std::map<std::string, std::string> setting_union_patches_enabled;
|
||||||
|
std::map<std::string, int64_t> setting_int_patches_enabled;
|
||||||
|
|
||||||
|
std::string patch_url;
|
||||||
|
|
||||||
|
std::filesystem::path LOCAL_PATCHES_PATH("patches");
|
||||||
|
std::string ACTIVE_JSON_FILE;
|
||||||
|
|
||||||
|
std::vector<PatchData> patches;
|
||||||
|
bool local_patches_initialized = false;
|
||||||
|
std::vector<size_t> patches_sorted;
|
||||||
|
std::map<std::pair<std::string, std::string>, PatchGroup> patch_groups;
|
||||||
|
std::map<std::string, std::vector<std::string>> EXTRA_DLLS = {
|
||||||
|
{"jubeat.dll", {"music_db.dll", "coin.dll"}},
|
||||||
|
{"arkmdxp3.dll", {"gamemdx.dll"}},
|
||||||
|
{"arkmdxp4.dll", {"gamemdx.dll"}},
|
||||||
|
{"arkmdxbio2.dll", {"gamemdx.dll"}},
|
||||||
|
{"arkndd.dll", {"gamendd.dll"}},
|
||||||
|
{"arkkep.dll", {"game.dll"}},
|
||||||
|
{"arkjc9.dll", {"gamejc9.dll"}},
|
||||||
|
{"arkkdm.dll", {"gamekdm.dll"}},
|
||||||
|
{"arkmmd.dll", {"gamemmd.dll"}},
|
||||||
|
{"arkklp.dll", {"lpac.dll"}},
|
||||||
|
{"arknck.dll", {"weac.dll"}},
|
||||||
|
{"gdxg.dll", {"game.dll", "libshare-pj.dll", "boot.dll"}}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string url_fetch_errors;
|
||||||
|
size_t url_recent_idx = -1;
|
||||||
|
std::vector<std::string> url_recents;
|
||||||
|
|
||||||
|
bool ldr_registered = false;
|
||||||
|
void *ldr_notify_cookie = nullptr;
|
||||||
|
std::vector<std::string> ldr_target_libraries;
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
if (PATCH_MANAGER_CFG_PATH_OVERRIDE.has_value()) {
|
||||||
|
config_path = PATCH_MANAGER_CFG_PATH_OVERRIDE.value();
|
||||||
|
log_info("patchmanager", "using custom config file path: {}", config_path);
|
||||||
|
} else {
|
||||||
|
config_path =
|
||||||
|
fileutils::get_config_file_path("patchmanager", "spicetools_patch_manager.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
// register for DLL load notifications so patches can be (re)applied as
|
||||||
|
// the game's target libraries come into memory. registration happens
|
||||||
|
// once and is left in place for the lifetime of the process.
|
||||||
|
if (!ldr_registered) {
|
||||||
|
ldr_target_libraries = getExtraDlls(avs::game::DLL_NAME);
|
||||||
|
ldr_target_libraries.push_back(avs::game::DLL_NAME);
|
||||||
|
|
||||||
|
const auto register_fn = reinterpret_cast<decltype(&LdrRegisterDllNotification)>(
|
||||||
|
GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "LdrRegisterDllNotification"));
|
||||||
|
|
||||||
|
if (register_fn && NT_SUCCESS(register_fn(
|
||||||
|
0, &loader_notification, nullptr, &ldr_notify_cookie))) {
|
||||||
|
log_info("patchmanager", "registered for DLL load notifications");
|
||||||
|
} else {
|
||||||
|
log_warning("patchmanager", "failed to register for DLL load notifications");
|
||||||
|
}
|
||||||
|
|
||||||
|
ldr_registered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!local_patches_initialized) {
|
||||||
|
patch_url.clear();
|
||||||
|
if (fileutils::file_exists(config_path)) {
|
||||||
|
config_load();
|
||||||
|
}
|
||||||
|
if (cfg::CONFIGURATOR_STANDALONE) {
|
||||||
|
reload_local_patches(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply_patches_on_start() {
|
||||||
|
if (!local_patches_initialized) {
|
||||||
|
reload_local_patches(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID CALLBACK loader_notification(
|
||||||
|
ULONG reason,
|
||||||
|
PCLDR_DLL_NOTIFICATION_DATA data,
|
||||||
|
PVOID) {
|
||||||
|
|
||||||
|
if (reason == LDR_DLL_NOTIFICATION_REASON_LOADED) {
|
||||||
|
const auto dll = strtolower(std::filesystem::path({
|
||||||
|
data->Loaded.FullDllName->Buffer,
|
||||||
|
data->Loaded.FullDllName->Length / sizeof(wchar_t)
|
||||||
|
}).filename().string());
|
||||||
|
|
||||||
|
if (std::ranges::find(ldr_target_libraries, dll) != ldr_target_libraries.end()) {
|
||||||
|
reload_local_patches(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,179 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include "util/logging.h"
|
||||||
|
#include "util/utils.h"
|
||||||
|
|
||||||
|
namespace patcher {
|
||||||
|
|
||||||
|
static std::pair<std::string, std::string> make_patch_group_key(
|
||||||
|
const std::string& game_code,
|
||||||
|
const std::string& group_id) {
|
||||||
|
return {strtolower(game_code), group_id};
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool has_embedded_null(const rapidjson::Value& value) {
|
||||||
|
return strlen(value.GetString()) != value.GetStringLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_patch_group_definition(const rapidjson::Value& patch) {
|
||||||
|
if (!patch.IsObject()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto type_it = patch.FindMember("type");
|
||||||
|
return type_it != patch.MemberEnd()
|
||||||
|
&& type_it->value.IsString()
|
||||||
|
&& type_it->value.GetStringLength() == strlen("group")
|
||||||
|
&& !_stricmp(type_it->value.GetString(), "group");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::pair<std::string, std::string>, PatchGroup> parse_patch_group_definitions(
|
||||||
|
const rapidjson::Document& doc) {
|
||||||
|
std::map<std::pair<std::string, std::string>, PatchGroup> groups;
|
||||||
|
|
||||||
|
for (const auto& patch : doc.GetArray()) {
|
||||||
|
if (!is_patch_group_definition(patch)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto id_it = patch.FindMember("id");
|
||||||
|
const auto game_code_it = patch.FindMember("gameCode");
|
||||||
|
const auto name_it = patch.FindMember("name");
|
||||||
|
if (id_it == patch.MemberEnd() || !id_it->value.IsString()
|
||||||
|
|| id_it->value.GetStringLength() == 0
|
||||||
|
|| has_embedded_null(id_it->value)
|
||||||
|
|| game_code_it == patch.MemberEnd() || !game_code_it->value.IsString()
|
||||||
|
|| game_code_it->value.GetStringLength() == 0
|
||||||
|
|| has_embedded_null(game_code_it->value)
|
||||||
|
|| name_it == patch.MemberEnd() || !name_it->value.IsString()
|
||||||
|
|| name_it->value.GetStringLength() == 0
|
||||||
|
|| has_embedded_null(name_it->value)) {
|
||||||
|
log_warning("patchmanager", "invalid patch group definition");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PatchGroup group;
|
||||||
|
group.name.assign(name_it->value.GetString(), name_it->value.GetStringLength());
|
||||||
|
group.name_in_lower_case = strtolower(group.name);
|
||||||
|
|
||||||
|
const std::string group_id(
|
||||||
|
id_it->value.GetString(),
|
||||||
|
id_it->value.GetStringLength());
|
||||||
|
|
||||||
|
const auto description_it = patch.FindMember("description");
|
||||||
|
if (description_it != patch.MemberEnd()) {
|
||||||
|
if (!description_it->value.IsString()
|
||||||
|
|| has_embedded_null(description_it->value)) {
|
||||||
|
log_warning("patchmanager", "invalid description for patch group {}", group_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
group.description.assign(
|
||||||
|
description_it->value.GetString(),
|
||||||
|
description_it->value.GetStringLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto caution_it = patch.FindMember("caution");
|
||||||
|
if (caution_it != patch.MemberEnd()) {
|
||||||
|
if (!caution_it->value.IsString() || has_embedded_null(caution_it->value)) {
|
||||||
|
log_warning("patchmanager", "invalid caution for patch group {}", group_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
group.caution.assign(
|
||||||
|
caution_it->value.GetString(),
|
||||||
|
caution_it->value.GetStringLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string game_code(
|
||||||
|
game_code_it->value.GetString(),
|
||||||
|
game_code_it->value.GetStringLength());
|
||||||
|
if (!groups.emplace(
|
||||||
|
make_patch_group_key(game_code, group_id),
|
||||||
|
std::move(group)).second) {
|
||||||
|
log_warning(
|
||||||
|
"patchmanager",
|
||||||
|
"duplicate patch group definition for {}/{}, ignoring duplicate",
|
||||||
|
game_code,
|
||||||
|
group_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const PatchGroup* find_patch_group(
|
||||||
|
const std::map<std::pair<std::string, std::string>, PatchGroup>& groups,
|
||||||
|
const std::string& game_code,
|
||||||
|
const std::string& group_id) {
|
||||||
|
const auto group = groups.find(make_patch_group_key(game_code, group_id));
|
||||||
|
return group == groups.end() ? nullptr : &group->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PatchGroup* find_patch_group(const PatchData& patch) {
|
||||||
|
return find_patch_group(patch_groups, patch.game_code, patch.group_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string resolve_patch_group_id(
|
||||||
|
const rapidjson::Value& patch,
|
||||||
|
const std::map<std::pair<std::string, std::string>, PatchGroup>& groups,
|
||||||
|
const std::string& game_code,
|
||||||
|
const char *patch_name) {
|
||||||
|
const auto group_it = patch.FindMember("group");
|
||||||
|
if (group_it == patch.MemberEnd()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (!group_it->value.IsString()
|
||||||
|
|| group_it->value.GetStringLength() == 0
|
||||||
|
|| has_embedded_null(group_it->value)) {
|
||||||
|
log_warning("patchmanager", "invalid group reference for {}", patch_name);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string group_id(
|
||||||
|
group_it->value.GetString(),
|
||||||
|
group_it->value.GetStringLength());
|
||||||
|
if (!find_patch_group(groups, game_code, group_id)) {
|
||||||
|
log_warning(
|
||||||
|
"patchmanager",
|
||||||
|
"unknown patch group {}/{} referenced by {}",
|
||||||
|
game_code,
|
||||||
|
group_id,
|
||||||
|
patch_name);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return group_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void register_patch_group(
|
||||||
|
PatchData& patch,
|
||||||
|
const std::map<std::pair<std::string, std::string>, PatchGroup>& definitions) {
|
||||||
|
if (patch.group_id.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto *definition = find_patch_group(
|
||||||
|
definitions,
|
||||||
|
patch.game_code,
|
||||||
|
patch.group_id);
|
||||||
|
if (!definition) {
|
||||||
|
patch.group_id.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto key = make_patch_group_key(patch.game_code, patch.group_id);
|
||||||
|
const auto [existing, inserted] = patch_groups.emplace(key, *definition);
|
||||||
|
if (!inserted
|
||||||
|
&& (existing->second.name != definition->name
|
||||||
|
|| existing->second.description != definition->description
|
||||||
|
|| existing->second.caution != definition->caution)) {
|
||||||
|
log_warning(
|
||||||
|
"patchmanager",
|
||||||
|
"conflicting group metadata for {}/{}, ignoring group on {}",
|
||||||
|
patch.game_code,
|
||||||
|
patch.group_id,
|
||||||
|
patch.name);
|
||||||
|
patch.group_id.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace patcher {
|
||||||
|
|
||||||
|
enum class PatchType {
|
||||||
|
Unknown,
|
||||||
|
Memory,
|
||||||
|
Signature,
|
||||||
|
Union,
|
||||||
|
Integer,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class PatchStatus {
|
||||||
|
Error,
|
||||||
|
Disabled,
|
||||||
|
Enabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class PatchUrlStatus {
|
||||||
|
Valid,
|
||||||
|
Invalid,
|
||||||
|
Unapplied,
|
||||||
|
ValidButNoData,
|
||||||
|
Partial,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MemoryPatch {
|
||||||
|
std::string dll_name = "";
|
||||||
|
std::shared_ptr<uint8_t[]> data_disabled = nullptr;
|
||||||
|
size_t data_disabled_len = 0;
|
||||||
|
std::shared_ptr<uint8_t[]> data_enabled = nullptr;
|
||||||
|
size_t data_enabled_len = 0;
|
||||||
|
uint64_t data_offset = 0;
|
||||||
|
uint8_t *data_offset_ptr = nullptr;
|
||||||
|
bool fatal_error = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PatchData;
|
||||||
|
struct SignaturePatch {
|
||||||
|
std::string dll_name = "";
|
||||||
|
std::string signature = "", replacement = "";
|
||||||
|
uint64_t offset = 0;
|
||||||
|
int64_t usage = 0;
|
||||||
|
|
||||||
|
MemoryPatch to_memory(PatchData *patch);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UnionPatch {
|
||||||
|
std::string name = "";
|
||||||
|
std::string dll_name = "";
|
||||||
|
std::shared_ptr<uint8_t[]> data = nullptr;
|
||||||
|
size_t data_len = 0;
|
||||||
|
uint64_t offset = 0;
|
||||||
|
uint8_t *data_offset_ptr = nullptr;
|
||||||
|
bool fatal_error = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NumberPatch {
|
||||||
|
std::string dll_name = "";
|
||||||
|
uint64_t data_offset = 0;
|
||||||
|
uint8_t *data_offset_ptr = nullptr;
|
||||||
|
int32_t min;
|
||||||
|
int32_t max;
|
||||||
|
int32_t value;
|
||||||
|
size_t size_in_bytes;
|
||||||
|
bool fatal_error = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PatchGroup {
|
||||||
|
std::string name;
|
||||||
|
std::string description;
|
||||||
|
std::string caution;
|
||||||
|
std::string name_in_lower_case;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PatchData {
|
||||||
|
bool enabled;
|
||||||
|
std::string game_code;
|
||||||
|
int datecode_min = 0;
|
||||||
|
int datecode_max = 0;
|
||||||
|
std::string name, description, caution;
|
||||||
|
std::string name_in_lower_case = "";
|
||||||
|
PatchType type;
|
||||||
|
bool preset;
|
||||||
|
std::vector<MemoryPatch> patches_memory;
|
||||||
|
std::vector<UnionPatch> patches_union;
|
||||||
|
NumberPatch patch_number;
|
||||||
|
std::string group_id;
|
||||||
|
PatchStatus last_status;
|
||||||
|
std::string hash;
|
||||||
|
bool unverified = false;
|
||||||
|
std::string peIdentifier;
|
||||||
|
std::string error_reason = "";
|
||||||
|
|
||||||
|
// for union patch only
|
||||||
|
std::string selected_union_name = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
extern std::optional<std::string> PATCH_MANAGER_CFG_PATH_OVERRIDE;
|
||||||
|
|
||||||
|
std::string get_game_identifier(const std::filesystem::path& dll_path, bool print_info = false);
|
||||||
|
|
||||||
|
// lifecycle: init() sets up the config path and registers for DLL-load
|
||||||
|
// notifications; apply_patches_on_start() applies auto-apply patches once the
|
||||||
|
// game DLL is loaded. both are safe to call repeatedly.
|
||||||
|
void init();
|
||||||
|
void apply_patches_on_start();
|
||||||
|
|
||||||
|
// patch load / apply operations (also driven by the overlay window)
|
||||||
|
void reload_local_patches(bool apply_patches = false);
|
||||||
|
bool import_remote_patches_to_disk();
|
||||||
|
void hard_apply_patches();
|
||||||
|
void config_load();
|
||||||
|
void config_save();
|
||||||
|
std::vector<std::string> getExtraDlls(const std::string& firstDll);
|
||||||
|
|
||||||
|
// process-wide patch engine state, rendered and edited by the overlay window
|
||||||
|
extern std::filesystem::path config_path;
|
||||||
|
extern bool config_dirty;
|
||||||
|
extern bool setting_auto_apply;
|
||||||
|
extern std::string patch_url;
|
||||||
|
extern std::string ACTIVE_JSON_FILE;
|
||||||
|
extern std::vector<PatchData> patches;
|
||||||
|
extern bool local_patches_initialized;
|
||||||
|
extern std::vector<size_t> patches_sorted;
|
||||||
|
extern std::string url_fetch_errors;
|
||||||
|
extern size_t url_recent_idx;
|
||||||
|
extern std::vector<std::string> url_recents;
|
||||||
|
|
||||||
|
PatchStatus is_patch_active(PatchData& patch);
|
||||||
|
bool apply_patch(PatchData& patch, bool active);
|
||||||
|
const PatchGroup* find_patch_group(const PatchData& patch);
|
||||||
|
|
||||||
|
std::string displayPath(const std::filesystem::path& path);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
@@ -103,6 +102,13 @@ namespace rawinput {
|
|||||||
std::vector<HIDTouchPoint> touch_points;
|
std::vector<HIDTouchPoint> touch_points;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct HIDButtonInputGroup {
|
||||||
|
USAGE usage_page;
|
||||||
|
USHORT link_collection;
|
||||||
|
std::vector<size_t> cap_indices;
|
||||||
|
std::vector<USAGE> usages;
|
||||||
|
};
|
||||||
|
|
||||||
struct DeviceHIDInfo {
|
struct DeviceHIDInfo {
|
||||||
HANDLE handle;
|
HANDLE handle;
|
||||||
_HIDP_CAPS caps;
|
_HIDP_CAPS caps;
|
||||||
@@ -125,11 +131,12 @@ namespace rawinput {
|
|||||||
|
|
||||||
std::vector<std::vector<bool>> button_states;
|
std::vector<std::vector<bool>> button_states;
|
||||||
std::vector<std::vector<double>> button_up, button_down;
|
std::vector<std::vector<double>> button_up, button_down;
|
||||||
|
std::vector<std::vector<uint8_t>> button_report_states;
|
||||||
std::vector<std::vector<bool>> button_output_states;
|
std::vector<std::vector<bool>> button_output_states;
|
||||||
|
std::vector<HIDButtonInputGroup> button_input_groups;
|
||||||
// key: usage page, link collection
|
std::vector<CHAR> output_report;
|
||||||
// value: number of buttons for that key (combine ranges and nonranges)
|
std::vector<USAGE> button_output_usages;
|
||||||
std::map<std::pair<USAGE, ULONG>, ULONG> button_usage_pages;
|
std::vector<USAGE> button_output_usages_off;
|
||||||
|
|
||||||
std::vector<float> value_states;
|
std::vector<float> value_states;
|
||||||
std::vector<LONG> value_states_raw;
|
std::vector<LONG> value_states_raw;
|
||||||
@@ -197,8 +204,8 @@ namespace rawinput {
|
|||||||
HANDLE handle = INVALID_HANDLE_VALUE;
|
HANDLE handle = INVALID_HANDLE_VALUE;
|
||||||
DeviceType type = UNKNOWN;
|
DeviceType type = UNKNOWN;
|
||||||
DeviceInfo info;
|
DeviceInfo info;
|
||||||
std::mutex *mutex;
|
std::mutex *mutex = nullptr;
|
||||||
std::mutex *mutex_out;
|
std::mutex *mutex_out = nullptr;
|
||||||
bool updated = true;
|
bool updated = true;
|
||||||
bool output_pending = true;
|
bool output_pending = true;
|
||||||
bool output_enabled = false;
|
bool output_enabled = false;
|
||||||
@@ -213,5 +220,7 @@ namespace rawinput {
|
|||||||
double input_time = 0.0;
|
double input_time = 0.0;
|
||||||
double input_hz = 0.f;
|
double input_hz = 0.f;
|
||||||
double input_hz_max = 0.f;
|
double input_hz_max = 0.f;
|
||||||
|
|
||||||
|
// when adding or removing a field, update replace_device_slot to match
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
#include <dbt.h>
|
#include <dbt.h>
|
||||||
#include <devguid.h>
|
#include <devguid.h>
|
||||||
|
#include <hidclass.h>
|
||||||
|
#include <ntddkbd.h>
|
||||||
|
#include <ntddmou.h>
|
||||||
|
#include <objbase.h>
|
||||||
|
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "rawinput.h"
|
#include "rawinput.h"
|
||||||
@@ -12,31 +16,42 @@
|
|||||||
|
|
||||||
namespace rawinput {
|
namespace rawinput {
|
||||||
|
|
||||||
DEFINE_GUID(GUID_HID, 0x4D1E55B2L, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30);
|
static bool is_rawinput_interface(const GUID &guid) {
|
||||||
|
return IsEqualGUID(guid, GUID_DEVINTERFACE_HID)
|
||||||
|
|| IsEqualGUID(guid, GUID_DEVINTERFACE_KEYBOARD)
|
||||||
|
|| IsEqualGUID(guid, GUID_DEVINTERFACE_MOUSE);
|
||||||
|
}
|
||||||
|
|
||||||
HotplugManager::HotplugManager(RawInputManager *ri_mgr, HWND hWnd) : ri_mgr(ri_mgr) {
|
HotplugManager::HotplugManager(RawInputManager *ri_mgr, HWND hWnd) : ri_mgr(ri_mgr) {
|
||||||
|
auto register_notification = [hWnd](const GUID &guid, const char *name) -> HANDLE {
|
||||||
|
DEV_BROADCAST_DEVICEINTERFACE settings {
|
||||||
|
.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE),
|
||||||
|
.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE,
|
||||||
|
.dbcc_reserved = 0,
|
||||||
|
.dbcc_classguid = guid,
|
||||||
|
.dbcc_name = {0},
|
||||||
|
};
|
||||||
|
|
||||||
// init settings
|
auto notification = RegisterDeviceNotification(hWnd, &settings, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||||
DEV_BROADCAST_DEVICEINTERFACE settings_hid {
|
if (notification == nullptr) {
|
||||||
.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE),
|
log_warning("hotplug", "failed to register {} notifications: {}", name, GetLastError());
|
||||||
.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE,
|
}
|
||||||
.dbcc_reserved = 0,
|
return notification;
|
||||||
.dbcc_classguid = GUID_HID,
|
|
||||||
.dbcc_name = {0},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// register notifications
|
// register notifications
|
||||||
this->hotplug_hid = RegisterDeviceNotification(hWnd, &settings_hid, DEVICE_NOTIFY_WINDOW_HANDLE);
|
this->hotplug_hid = register_notification(GUID_DEVINTERFACE_HID, "HID");
|
||||||
if (this->hotplug_hid == nullptr) {
|
this->hotplug_keyboard = register_notification(GUID_DEVINTERFACE_KEYBOARD, "keyboard");
|
||||||
log_warning("hotplug", "failed to register HID notifications: {}", GetLastError());
|
this->hotplug_mouse = register_notification(GUID_DEVINTERFACE_MOUSE, "mouse");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HotplugManager::~HotplugManager() {
|
HotplugManager::~HotplugManager() {
|
||||||
|
|
||||||
// unregister notifications
|
// unregister notifications
|
||||||
if (this->hotplug_hid != nullptr) {
|
for (auto notification : {this->hotplug_hid, this->hotplug_keyboard, this->hotplug_mouse}) {
|
||||||
UnregisterDeviceNotification(this->hotplug_hid);
|
if (notification != nullptr) {
|
||||||
|
UnregisterDeviceNotification(notification);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,11 +69,16 @@ namespace rawinput {
|
|||||||
switch (hdr->dbch_devicetype) {
|
switch (hdr->dbch_devicetype) {
|
||||||
case DBT_DEVTYP_DEVICEINTERFACE: {
|
case DBT_DEVTYP_DEVICEINTERFACE: {
|
||||||
auto dev = (const DEV_BROADCAST_DEVICEINTERFACE *) hdr;
|
auto dev = (const DEV_BROADCAST_DEVICEINTERFACE *) hdr;
|
||||||
this->ri_mgr->midi_scan_start();
|
|
||||||
|
|
||||||
// check if class is not HID
|
// check if this is one of the registered RawInput interfaces
|
||||||
if (memcmp(&dev->dbcc_classguid, &GUID_HID, sizeof(GUID_HID)) != 0)
|
if (!is_rawinput_interface(dev->dbcc_classguid)) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyboard and mouse class events accompany the HID event, so scan MIDI only once
|
||||||
|
if (IsEqualGUID(dev->dbcc_classguid, GUID_DEVINTERFACE_HID)) {
|
||||||
|
this->ri_mgr->midi_scan_start();
|
||||||
|
}
|
||||||
|
|
||||||
// hotplug
|
// hotplug
|
||||||
std::string name(dev->dbcc_name);
|
std::string name(dev->dbcc_name);
|
||||||
@@ -109,6 +129,9 @@ namespace rawinput {
|
|||||||
switch (hdr->dbch_devicetype) {
|
switch (hdr->dbch_devicetype) {
|
||||||
case DBT_DEVTYP_DEVICEINTERFACE: {
|
case DBT_DEVTYP_DEVICEINTERFACE: {
|
||||||
auto dev = (DEV_BROADCAST_DEVICEINTERFACE*) hdr;
|
auto dev = (DEV_BROADCAST_DEVICEINTERFACE*) hdr;
|
||||||
|
if (!is_rawinput_interface(dev->dbcc_classguid)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
std::string name(dev->dbcc_name);
|
std::string name(dev->dbcc_name);
|
||||||
|
|
||||||
// destruct device
|
// destruct device
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ namespace rawinput {
|
|||||||
class HotplugManager {
|
class HotplugManager {
|
||||||
private:
|
private:
|
||||||
RawInputManager *ri_mgr;
|
RawInputManager *ri_mgr;
|
||||||
HANDLE hotplug_hid;
|
HANDLE hotplug_hid = nullptr;
|
||||||
|
HANDLE hotplug_keyboard = nullptr;
|
||||||
|
HANDLE hotplug_mouse = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
HotplugManager(RawInputManager *ri_mgr, HWND hwnd);
|
HotplugManager(RawInputManager *ri_mgr, HWND hwnd);
|
||||||
|
|||||||
@@ -238,8 +238,6 @@ void rawinput::RawInputManager::devices_scan_midi() {
|
|||||||
midi_device.name = midi_identifier;
|
midi_device.name = midi_identifier;
|
||||||
midi_device.desc = to_string(midi_device_caps.szPname);
|
midi_device.desc = to_string(midi_device_caps.szPname);
|
||||||
midi_device.info = midi_device_info;
|
midi_device.info = midi_device_info;
|
||||||
midi_device.mutex = new std::mutex();
|
|
||||||
midi_device.mutex_out = new std::mutex();
|
|
||||||
midi_device.midiInfo = midi_device_midi_info;
|
midi_device.midiInfo = midi_device_midi_info;
|
||||||
|
|
||||||
// mutate the shared device list under lock (the slow WinMM calls above
|
// mutate the shared device list under lock (the slow WinMM calls above
|
||||||
@@ -256,10 +254,9 @@ void rawinput::RawInputManager::devices_scan_midi() {
|
|||||||
// carry over ID
|
// carry over ID
|
||||||
midi_device.id = device.id;
|
midi_device.id = device.id;
|
||||||
|
|
||||||
// destruct and replace, reusing the slot's existing mutexes
|
// destruct and replace the slot in place under its locks
|
||||||
this->devices_destruct(&device);
|
this->devices_destruct(&device);
|
||||||
reuse_device_mutexes(midi_device, device);
|
replace_device_slot(device, midi_device);
|
||||||
device = midi_device;
|
|
||||||
|
|
||||||
// notify change
|
// notify change
|
||||||
for (auto &cb : this->callback_change) {
|
for (auto &cb : this->callback_change) {
|
||||||
@@ -275,6 +272,8 @@ void rawinput::RawInputManager::devices_scan_midi() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add device to list
|
// add device to list
|
||||||
|
midi_device.mutex = new std::mutex();
|
||||||
|
midi_device.mutex_out = new std::mutex();
|
||||||
auto &device = this->devices.emplace_back(midi_device);
|
auto &device = this->devices.emplace_back(midi_device);
|
||||||
|
|
||||||
// notify add
|
// notify add
|
||||||
|
|||||||
+152
-113
@@ -1,6 +1,10 @@
|
|||||||
#include "rawinput.h"
|
#include "rawinput.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
|
#include <map>
|
||||||
|
#include <thread>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -9,7 +13,6 @@
|
|||||||
|
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "external/robin_hood.h"
|
#include "external/robin_hood.h"
|
||||||
#include "util/precise_timer.h"
|
|
||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
@@ -44,18 +47,40 @@ namespace rawinput {
|
|||||||
bool OS_WINDOW_ACTIVE = false;
|
bool OS_WINDOW_ACTIVE = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when replacing a device slot in place, keep the old slot's per-device mutexes
|
// serialize replacement with input and output users, matching teardown lock order
|
||||||
// instead of the freshly allocated pair on `replacement`. their addresses stay
|
void rawinput::RawInputManager::replace_device_slot(Device &existing, Device &replacement) {
|
||||||
// stable, so a thread still holding a snapshot pointer to the slot (e.g. the
|
if (existing.mutex == nullptr || existing.mutex_out == nullptr) {
|
||||||
// output thread blocked on mutex_out, which is taken without devices_mutex)
|
log_fatal("rawinput", "existing device slot has no mutexes");
|
||||||
// never locks freed memory. the freshly allocated pair is freed here rather
|
}
|
||||||
// than leaking the old one. the slot must already be destructed so both
|
if (replacement.mutex != nullptr || replacement.mutex_out != nullptr) {
|
||||||
// mutexes are unlocked
|
log_fatal("rawinput", "replacement device already has mutexes");
|
||||||
void rawinput::RawInputManager::reuse_device_mutexes(Device &replacement, const Device &existing) {
|
}
|
||||||
delete replacement.mutex;
|
std::lock_guard<std::mutex> lock_out(*existing.mutex_out);
|
||||||
delete replacement.mutex_out;
|
std::lock_guard<std::mutex> lock(*existing.mutex);
|
||||||
replacement.mutex = existing.mutex;
|
|
||||||
replacement.mutex_out = existing.mutex_out;
|
// copy every field except the stable mutex pointers: snapshot users may be blocked
|
||||||
|
// on those exact locks without devices_mutex, so rewriting the pointers could send a
|
||||||
|
// waiter to a different mutex. when adding a field, add it here too (see device.h)
|
||||||
|
existing.id = replacement.id;
|
||||||
|
existing.name = std::move(replacement.name);
|
||||||
|
existing.desc = std::move(replacement.desc);
|
||||||
|
existing.handle = replacement.handle;
|
||||||
|
existing.type = replacement.type;
|
||||||
|
existing.info = replacement.info;
|
||||||
|
existing.updated = replacement.updated;
|
||||||
|
existing.output_pending = replacement.output_pending;
|
||||||
|
existing.output_enabled = replacement.output_enabled;
|
||||||
|
existing.mouseInfo = replacement.mouseInfo;
|
||||||
|
existing.keyboardInfo = replacement.keyboardInfo;
|
||||||
|
existing.hidInfo = replacement.hidInfo;
|
||||||
|
existing.midiInfo = replacement.midiInfo;
|
||||||
|
existing.sextetInfo = replacement.sextetInfo;
|
||||||
|
existing.piuioDev = replacement.piuioDev;
|
||||||
|
existing.smxstageInfo = replacement.smxstageInfo;
|
||||||
|
existing.smxdedicabInfo = replacement.smxdedicabInfo;
|
||||||
|
existing.input_time = replacement.input_time;
|
||||||
|
existing.input_hz = replacement.input_hz;
|
||||||
|
existing.input_hz_max = replacement.input_hz_max;
|
||||||
}
|
}
|
||||||
|
|
||||||
rawinput::RawInputManager::RawInputManager() {
|
rawinput::RawInputManager::RawInputManager() {
|
||||||
@@ -147,9 +172,8 @@ void rawinput::RawInputManager::input_hwnd_create() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// wait for window creation being done
|
// wait for window creation being done
|
||||||
timeutils::PreciseSleepTimer timer;
|
|
||||||
while (!this->input_hwnd) {
|
while (!this->input_hwnd) {
|
||||||
timer.sleep(1);
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +248,8 @@ void rawinput::RawInputManager::devices_scan_rawinput(const std::string &device_
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterate devices
|
// hotplug gives us an interface path rather than a RawInput handle, so find its
|
||||||
|
// matching entry; an empty path means this is a full device scan
|
||||||
for (UINT device_cur_index = 0; device_cur_index < device_no; device_cur_index++) {
|
for (UINT device_cur_index = 0; device_cur_index < device_no; device_cur_index++) {
|
||||||
auto device = &device_list.get()[device_cur_index];
|
auto device = &device_list.get()[device_cur_index];
|
||||||
if (device_name.length() == 0) {
|
if (device_name.length() == 0) {
|
||||||
@@ -277,8 +302,6 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
|||||||
new_device.name = device_name;
|
new_device.name = device_name;
|
||||||
new_device.desc = device_description;
|
new_device.desc = device_description;
|
||||||
new_device.info = device_info;
|
new_device.info = device_info;
|
||||||
new_device.mutex = new std::mutex();
|
|
||||||
new_device.mutex_out = new std::mutex();
|
|
||||||
new_device.input_time = get_performance_seconds();
|
new_device.input_time = get_performance_seconds();
|
||||||
switch (device->dwType) {
|
switch (device->dwType) {
|
||||||
case RIM_TYPEMOUSE:
|
case RIM_TYPEMOUSE:
|
||||||
@@ -418,7 +441,9 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
|||||||
std::vector<std::string> button_caps_names;
|
std::vector<std::string> button_caps_names;
|
||||||
std::vector<std::vector<bool>> button_states;
|
std::vector<std::vector<bool>> button_states;
|
||||||
std::vector<std::vector<double>> button_up, button_down;
|
std::vector<std::vector<double>> button_up, button_down;
|
||||||
std::map<std::pair<USAGE, ULONG>, ULONG> button_usage_pages;
|
std::vector<std::vector<uint8_t>> button_report_states;
|
||||||
|
std::vector<HIDButtonInputGroup> button_input_groups;
|
||||||
|
std::map<std::pair<USAGE, ULONG>, size_t> button_input_group_indices;
|
||||||
for (int button_cap_num = 0; button_cap_num < button_cap_length; button_cap_num++) {
|
for (int button_cap_num = 0; button_cap_num < button_cap_length; button_cap_num++) {
|
||||||
auto &button_caps = button_cap_data[button_cap_num];
|
auto &button_caps = button_cap_data[button_cap_num];
|
||||||
|
|
||||||
@@ -437,7 +462,7 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
|||||||
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
|
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
|
||||||
|
|
||||||
// ignore bad ranges reported by bad devices
|
// ignore bad ranges reported by bad devices
|
||||||
if (button_count >= 0xffff) {
|
if (button_count <= 0 || button_count >= 0xffff) {
|
||||||
log_warning("rawinput", "skipping bad button cap range for device {}, range [{}, {}]",
|
log_warning("rawinput", "skipping bad button cap range for device {}, range [{}, {}]",
|
||||||
device_name,
|
device_name,
|
||||||
button_caps.Range.UsageMin,
|
button_caps.Range.UsageMin,
|
||||||
@@ -446,11 +471,30 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fill vectors
|
// fill vectors
|
||||||
|
const size_t cap_index = button_caps_list.size();
|
||||||
button_caps_list.emplace_back(button_caps);
|
button_caps_list.emplace_back(button_caps);
|
||||||
button_states.emplace_back(std::vector<bool>(static_cast<unsigned int>(button_count), false));
|
button_states.emplace_back(std::vector<bool>(static_cast<unsigned int>(button_count), false));
|
||||||
button_up.emplace_back(std::vector<double>(static_cast<unsigned int>(button_count), 0.0));
|
button_up.emplace_back(std::vector<double>(static_cast<unsigned int>(button_count), 0.0));
|
||||||
button_down.emplace_back(std::vector<double>(static_cast<unsigned int>(button_count), 0.0));
|
button_down.emplace_back(std::vector<double>(static_cast<unsigned int>(button_count), 0.0));
|
||||||
button_usage_pages[std::make_pair(button_caps.UsagePage, button_caps.LinkCollection)] += button_count;
|
button_report_states.emplace_back(static_cast<size_t>(button_count), 0);
|
||||||
|
|
||||||
|
const auto group_key = std::make_pair(
|
||||||
|
button_caps.UsagePage,
|
||||||
|
static_cast<ULONG>(button_caps.LinkCollection));
|
||||||
|
auto [group_it, inserted] = button_input_group_indices.try_emplace(
|
||||||
|
group_key,
|
||||||
|
button_input_groups.size());
|
||||||
|
if (inserted) {
|
||||||
|
button_input_groups.emplace_back(HIDButtonInputGroup {
|
||||||
|
.usage_page = button_caps.UsagePage,
|
||||||
|
.link_collection = button_caps.LinkCollection,
|
||||||
|
.cap_indices = {},
|
||||||
|
.usages = {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
auto &input_group = button_input_groups[group_it->second];
|
||||||
|
input_group.cap_indices.push_back(cap_index);
|
||||||
|
input_group.usages.resize(input_group.usages.size() + button_count);
|
||||||
|
|
||||||
// names
|
// names
|
||||||
for (USAGE usg = button_caps.Range.UsageMin; usg <= button_caps.Range.UsageMax; usg++) {
|
for (USAGE usg = button_caps.Range.UsageMin; usg <= button_caps.Range.UsageMax; usg++) {
|
||||||
@@ -491,7 +535,7 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
|||||||
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
|
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
|
||||||
|
|
||||||
// ignore bad ranges reported by bad devices
|
// ignore bad ranges reported by bad devices
|
||||||
if (button_count >= 0xffff) {
|
if (button_count <= 0 || button_count >= 0xffff) {
|
||||||
log_warning("rawinput", "skipping bad button output cap range for device {}, range [{}, {}]",
|
log_warning("rawinput", "skipping bad button output cap range for device {}, range [{}, {}]",
|
||||||
device_name,
|
device_name,
|
||||||
button_caps.Range.UsageMin,
|
button_caps.Range.UsageMin,
|
||||||
@@ -779,8 +823,18 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
|||||||
new_device.hidInfo->button_states = std::move(button_states);
|
new_device.hidInfo->button_states = std::move(button_states);
|
||||||
new_device.hidInfo->button_up = std::move(button_up);
|
new_device.hidInfo->button_up = std::move(button_up);
|
||||||
new_device.hidInfo->button_down = std::move(button_down);
|
new_device.hidInfo->button_down = std::move(button_down);
|
||||||
|
new_device.hidInfo->button_report_states = std::move(button_report_states);
|
||||||
new_device.hidInfo->button_output_states = std::move(button_output_states);
|
new_device.hidInfo->button_output_states = std::move(button_output_states);
|
||||||
new_device.hidInfo->button_usage_pages = std::move(button_usage_pages);
|
new_device.hidInfo->button_input_groups = std::move(button_input_groups);
|
||||||
|
new_device.hidInfo->output_report.resize(caps.OutputReportByteLength);
|
||||||
|
size_t max_button_output_count = 0;
|
||||||
|
for (const auto &button_output_state : new_device.hidInfo->button_output_states) {
|
||||||
|
if (button_output_state.size() > max_button_output_count) {
|
||||||
|
max_button_output_count = button_output_state.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_device.hidInfo->button_output_usages.reserve(max_button_output_count);
|
||||||
|
new_device.hidInfo->button_output_usages_off.reserve(max_button_output_count);
|
||||||
new_device.hidInfo->value_states = std::move(value_states);
|
new_device.hidInfo->value_states = std::move(value_states);
|
||||||
new_device.hidInfo->value_states_raw = std::move(value_states_raw);
|
new_device.hidInfo->value_states_raw = std::move(value_states_raw);
|
||||||
new_device.hidInfo->value_output_states = std::move(value_output_states);
|
new_device.hidInfo->value_output_states = std::move(value_output_states);
|
||||||
@@ -805,10 +859,10 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
|||||||
// carry over old device ID
|
// carry over old device ID
|
||||||
new_device.id = prev_device.id;
|
new_device.id = prev_device.id;
|
||||||
|
|
||||||
// destruct and replace, reusing the slot's existing mutexes
|
// destruct and replace the slot in place under its locks
|
||||||
this->devices_destruct(&prev_device);
|
this->devices_destruct(&prev_device);
|
||||||
reuse_device_mutexes(new_device, prev_device);
|
replace_device_slot(prev_device, new_device);
|
||||||
prev_device = new_device;
|
this->rawinput_handles.add(&prev_device);
|
||||||
|
|
||||||
// notify change
|
// notify change
|
||||||
for (auto &cb : this->callback_change) {
|
for (auto &cb : this->callback_change) {
|
||||||
@@ -820,7 +874,10 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add device to list
|
// add device to list
|
||||||
|
new_device.mutex = new std::mutex();
|
||||||
|
new_device.mutex_out = new std::mutex();
|
||||||
auto &added_device = this->devices.emplace_back(new_device);
|
auto &added_device = this->devices.emplace_back(new_device);
|
||||||
|
this->rawinput_handles.add(&added_device);
|
||||||
if (log) {
|
if (log) {
|
||||||
log_info("rawinput", "added device: {} / {}", added_device.desc, added_device.name);
|
log_info("rawinput", "added device: {} / {}", added_device.desc, added_device.name);
|
||||||
}
|
}
|
||||||
@@ -951,8 +1008,6 @@ void rawinput::RawInputManager::devices_scan_xinput() {
|
|||||||
device.name = xinput::get_device_desc(player);
|
device.name = xinput::get_device_desc(player);
|
||||||
device.desc = fmt::format("XInput Gamepad P{}", player + 1);
|
device.desc = fmt::format("XInput Gamepad P{}", player + 1);
|
||||||
device.handle = reinterpret_cast<HANDLE>(player);
|
device.handle = reinterpret_cast<HANDLE>(player);
|
||||||
device.mutex = new std::mutex();
|
|
||||||
device.mutex_out = new std::mutex();
|
|
||||||
return device;
|
return device;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -969,11 +1024,10 @@ void rawinput::RawInputManager::devices_scan_xinput() {
|
|||||||
log_info("rawinput", "overwriting previously destroyed XInput device: {}", prev_device.name);
|
log_info("rawinput", "overwriting previously destroyed XInput device: {}", prev_device.name);
|
||||||
const auto old_id = prev_device.id;
|
const auto old_id = prev_device.id;
|
||||||
|
|
||||||
// replace in place, reusing the slot's existing mutexes
|
// replace the slot in place under its locks
|
||||||
auto replacement = create_device(player);
|
auto replacement = create_device(player);
|
||||||
reuse_device_mutexes(replacement, prev_device);
|
replacement.id = old_id;
|
||||||
prev_device = replacement;
|
replace_device_slot(prev_device, replacement);
|
||||||
prev_device.id = old_id;
|
|
||||||
|
|
||||||
// notify change
|
// notify change
|
||||||
for (auto &cb : this->callback_change) {
|
for (auto &cb : this->callback_change) {
|
||||||
@@ -989,6 +1043,8 @@ void rawinput::RawInputManager::devices_scan_xinput() {
|
|||||||
log_info("rawinput", "adding new XInput device: player {}", player + 1);
|
log_info("rawinput", "adding new XInput device: player {}", player + 1);
|
||||||
auto new_xinput_device = create_device(player);
|
auto new_xinput_device = create_device(player);
|
||||||
new_xinput_device.id = this->devices.size() + 1;
|
new_xinput_device.id = this->devices.size() + 1;
|
||||||
|
new_xinput_device.mutex = new std::mutex();
|
||||||
|
new_xinput_device.mutex_out = new std::mutex();
|
||||||
auto &device = this->devices.emplace_back(new_xinput_device);
|
auto &device = this->devices.emplace_back(new_xinput_device);
|
||||||
|
|
||||||
// notify add
|
// notify add
|
||||||
@@ -1438,6 +1494,8 @@ void rawinput::RawInputManager::devices_destruct() {
|
|||||||
|
|
||||||
void rawinput::RawInputManager::devices_destruct(Device *device, bool log) {
|
void rawinput::RawInputManager::devices_destruct(Device *device, bool log) {
|
||||||
|
|
||||||
|
this->rawinput_handles.remove(device);
|
||||||
|
|
||||||
// check if destroyed
|
// check if destroyed
|
||||||
if (device->type == DESTROYED) {
|
if (device->type == DESTROYED) {
|
||||||
return;
|
return;
|
||||||
@@ -1454,21 +1512,20 @@ void rawinput::RawInputManager::devices_destruct(Device *device, bool log) {
|
|||||||
// devices_mutex, so mutex_out is what actually serializes it against us
|
// devices_mutex, so mutex_out is what actually serializes it against us
|
||||||
std::lock_guard<std::mutex> lock_out(*device->mutex_out);
|
std::lock_guard<std::mutex> lock_out(*device->mutex_out);
|
||||||
|
|
||||||
// mark as destroyed
|
// mark the device destroyed while synchronized with in-flight input
|
||||||
auto device_type = device->type;
|
DeviceType device_type;
|
||||||
device->type = DESTROYED;
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(*device->mutex);
|
||||||
|
device_type = device->type;
|
||||||
|
device->type = DESTROYED;
|
||||||
|
}
|
||||||
|
|
||||||
// notify change
|
// callbacks may lock the device mutex
|
||||||
for (auto &cb : this->callback_change) {
|
for (auto &cb : this->callback_change) {
|
||||||
cb.f(cb.data, device);
|
cb.f(cb.data, device);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// wait for input users before releasing device resources
|
||||||
* lock device
|
|
||||||
* note: this is an exception to only locking devices when we acquire them from the list
|
|
||||||
* callbacks could want to lock the mutex as well and it isn't recursive
|
|
||||||
* this also means the device must be unlocked before calling this function
|
|
||||||
*/
|
|
||||||
std::lock_guard<std::mutex> lock(*device->mutex);
|
std::lock_guard<std::mutex> lock(*device->mutex);
|
||||||
|
|
||||||
// close device handles
|
// close device handles
|
||||||
@@ -1549,11 +1606,17 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
|||||||
if (!data_size) {
|
if (!data_size) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::shared_ptr<RAWINPUT> data((RAWINPUT *) new char[data_size]{});
|
thread_local std::vector<RAWINPUT> data_buffer;
|
||||||
|
const size_t data_count =
|
||||||
|
(data_size + sizeof(RAWINPUT) - 1) / sizeof(RAWINPUT);
|
||||||
|
if (data_buffer.size() < data_count) {
|
||||||
|
data_buffer.resize(data_count);
|
||||||
|
}
|
||||||
|
auto data = data_buffer.data();
|
||||||
if (GetRawInputData(
|
if (GetRawInputData(
|
||||||
(HRAWINPUT) lParam,
|
(HRAWINPUT) lParam,
|
||||||
RID_INPUT,
|
RID_INPUT,
|
||||||
data.get(),
|
data,
|
||||||
&data_size,
|
&data_size,
|
||||||
sizeof(RAWINPUTHEADER)) != data_size) {
|
sizeof(RAWINPUTHEADER)) != data_size) {
|
||||||
break;
|
break;
|
||||||
@@ -1561,21 +1624,13 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
|||||||
|
|
||||||
// find device
|
// find device
|
||||||
HANDLE device_handle = data->header.hDevice;
|
HANDLE device_handle = data->header.hDevice;
|
||||||
// lock the device list so a concurrent scan can't mutate it while we iterate
|
auto acquired_device = ref->rawinput_handles.acquire(device_handle);
|
||||||
std::lock_guard<std::recursive_mutex> devices_lock(ref->devices_mutex);
|
if (acquired_device.device != nullptr) {
|
||||||
for (auto &device : ref->devices_get()) {
|
auto &device = *acquired_device.device;
|
||||||
|
|
||||||
// skip if this is the wrong device
|
|
||||||
if (device.handle != device_handle) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get input time
|
// get input time
|
||||||
const auto input_time = get_performance_seconds();
|
const auto input_time = get_performance_seconds();
|
||||||
|
|
||||||
// lock device
|
|
||||||
device.mutex->lock();
|
|
||||||
|
|
||||||
// update hz
|
// update hz
|
||||||
double diff_time = input_time - device.input_time;
|
double diff_time = input_time - device.input_time;
|
||||||
if (diff_time > 0.0001) {
|
if (diff_time > 0.0001) {
|
||||||
@@ -1775,17 +1830,13 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
|||||||
+ (size_t) hid_report_index * data_hid.dwSizeHid;
|
+ (size_t) hid_report_index * data_hid.dwSizeHid;
|
||||||
|
|
||||||
// parse reports
|
// parse reports
|
||||||
for (const auto &pair : device.hidInfo->button_usage_pages) {
|
for (auto &input_group : device.hidInfo->button_input_groups) {
|
||||||
const auto usage_page = pair.first.first;
|
auto &usages = input_group.usages;
|
||||||
const auto link_collection = pair.first.second;
|
ULONG usages_length = static_cast<ULONG>(usages.size());
|
||||||
const auto button_count = pair.second;
|
|
||||||
|
|
||||||
ULONG usages_length = button_count;
|
|
||||||
std::vector<USAGE> usages(static_cast<size_t>(usages_length));
|
|
||||||
if (HidP_GetUsages(
|
if (HidP_GetUsages(
|
||||||
HidP_Input,
|
HidP_Input,
|
||||||
usage_page,
|
input_group.usage_page,
|
||||||
link_collection,
|
input_group.link_collection,
|
||||||
usages.data(),
|
usages.data(),
|
||||||
&usages_length,
|
&usages_length,
|
||||||
reinterpret_cast<PHIDP_PREPARSED_DATA>(device.hidInfo->preparsed_data.get()),
|
reinterpret_cast<PHIDP_PREPARSED_DATA>(device.hidInfo->preparsed_data.get()),
|
||||||
@@ -1803,29 +1854,22 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
|||||||
// log_info(
|
// log_info(
|
||||||
// "rawinput",
|
// "rawinput",
|
||||||
// "processing HID input for device {}, usage page {:x} and link collection {:x} with {} buttons, got {} reports",
|
// "processing HID input for device {}, usage page {:x} and link collection {:x} with {} buttons, got {} reports",
|
||||||
// device.desc,
|
// device.desc, input_group.usage_page,
|
||||||
// usage_page, link_collection, button_count, usages_length);
|
// input_group.link_collection, usages.size(), usages_length);
|
||||||
|
|
||||||
// buttons
|
// buttons
|
||||||
for (size_t cap_num = 0; cap_num < device.hidInfo->button_caps_list.size(); cap_num++) {
|
for (const size_t cap_num : input_group.cap_indices) {
|
||||||
auto &button_caps = device.hidInfo->button_caps_list[cap_num];
|
auto &button_caps = device.hidInfo->button_caps_list[cap_num];
|
||||||
auto &button_states = device.hidInfo->button_states[cap_num];
|
auto &button_states = device.hidInfo->button_states[cap_num];
|
||||||
auto &button_down = device.hidInfo->button_down[cap_num];
|
auto &button_down = device.hidInfo->button_down[cap_num];
|
||||||
auto &button_up = device.hidInfo->button_up[cap_num];
|
auto &button_up = device.hidInfo->button_up[cap_num];
|
||||||
|
auto &new_states = device.hidInfo->button_report_states[cap_num];
|
||||||
// is this the right usage page and link collection?
|
|
||||||
if (button_caps.UsagePage != usage_page || button_caps.LinkCollection != link_collection) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get button count
|
// get button count
|
||||||
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
|
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
|
||||||
if (button_count <= 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update buttons
|
// update buttons
|
||||||
std::vector<bool> new_states(button_count);
|
std::fill(new_states.begin(), new_states.end(), 0);
|
||||||
for (ULONG usage_num = 0; usage_num < usages_length; usage_num++) {
|
for (ULONG usage_num = 0; usage_num < usages_length; usage_num++) {
|
||||||
if (usages[usage_num] < button_caps.Range.UsageMin ||
|
if (usages[usage_num] < button_caps.Range.UsageMin ||
|
||||||
usages[usage_num] > button_caps.Range.UsageMax) {
|
usages[usage_num] > button_caps.Range.UsageMax) {
|
||||||
@@ -1834,19 +1878,20 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
|||||||
|
|
||||||
USAGE usage = usages[usage_num] - button_caps.Range.UsageMin;
|
USAGE usage = usages[usage_num] - button_caps.Range.UsageMin;
|
||||||
|
|
||||||
// guard against some buggy device sending an event for a usage below `UsageMin`
|
// guard against some buggy device sending an event for a usage below UsageMin
|
||||||
if (usage < button_count) {
|
if (usage < button_count) {
|
||||||
new_states[usage] = true;
|
new_states[usage] = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int button_num = 0; button_num < button_count; button_num++) {
|
for (int button_num = 0; button_num < button_count; button_num++) {
|
||||||
if (!new_states[button_num] && button_states[button_num]) {
|
const bool new_state = new_states[button_num] != 0;
|
||||||
|
if (!new_state && button_states[button_num]) {
|
||||||
device.updated = true;
|
device.updated = true;
|
||||||
button_states[button_num] = new_states[button_num];
|
button_states[button_num] = new_state;
|
||||||
button_down[button_num] = input_time;
|
button_down[button_num] = input_time;
|
||||||
} else if (new_states[button_num] && !button_states[button_num]) {
|
} else if (new_state && !button_states[button_num]) {
|
||||||
device.updated = true;
|
device.updated = true;
|
||||||
button_states[button_num] = new_states[button_num];
|
button_states[button_num] = new_state;
|
||||||
button_up[button_num] = input_time;
|
button_up[button_num] = input_time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1937,11 +1982,7 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// free device
|
acquired_device.lock.unlock();
|
||||||
device.mutex->unlock();
|
|
||||||
|
|
||||||
// don't iterate through the other devices
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update controller state ring buffers (DDR/MDXF)
|
// update controller state ring buffers (DDR/MDXF)
|
||||||
@@ -1973,6 +2014,9 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
|||||||
|
|
||||||
void rawinput::RawInputManager::device_write_output(Device *device, bool only_updated) {
|
void rawinput::RawInputManager::device_write_output(Device *device, bool only_updated) {
|
||||||
|
|
||||||
|
// lock device
|
||||||
|
std::lock_guard<std::mutex> lock(*device->mutex_out);
|
||||||
|
|
||||||
// check if output is enabled
|
// check if output is enabled
|
||||||
if (!device->output_enabled) {
|
if (!device->output_enabled) {
|
||||||
return;
|
return;
|
||||||
@@ -1983,9 +2027,6 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// lock device
|
|
||||||
device->mutex_out->lock();
|
|
||||||
|
|
||||||
// mark device as updated
|
// mark device as updated
|
||||||
device->output_pending = false;
|
device->output_pending = false;
|
||||||
|
|
||||||
@@ -2005,8 +2046,10 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
|||||||
switch (hid->driver) {
|
switch (hid->driver) {
|
||||||
case HIDDriver::Default: {
|
case HIDDriver::Default: {
|
||||||
|
|
||||||
// allocate report
|
auto &report_data = hid->output_report;
|
||||||
CHAR *report_data = new CHAR[hid->caps.OutputReportByteLength] {};
|
auto &usage_list = hid->button_output_usages;
|
||||||
|
auto &usage_off_list = hid->button_output_usages_off;
|
||||||
|
std::fill(report_data.begin(), report_data.end(), 0);
|
||||||
|
|
||||||
// set buttons
|
// set buttons
|
||||||
for (size_t cap_no = 0; cap_no < hid->button_output_caps_list.size(); cap_no++) {
|
for (size_t cap_no = 0; cap_no < hid->button_output_caps_list.size(); cap_no++) {
|
||||||
@@ -2014,10 +2057,8 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
|||||||
auto &button_state_list = hid->button_output_states[cap_no];
|
auto &button_state_list = hid->button_output_states[cap_no];
|
||||||
|
|
||||||
// determine which buttons to turn on
|
// determine which buttons to turn on
|
||||||
std::vector<USAGE> usage_list;
|
usage_list.clear();
|
||||||
std::vector<USAGE> usage_off_list;
|
usage_off_list.clear();
|
||||||
usage_list.reserve(button_state_list.size());
|
|
||||||
usage_off_list.reserve(button_state_list.size());
|
|
||||||
for (size_t state_no = 0; state_no < button_state_list.size(); state_no++) {
|
for (size_t state_no = 0; state_no < button_state_list.size(); state_no++) {
|
||||||
if (button_state_list[state_no]) {
|
if (button_state_list[state_no]) {
|
||||||
usage_list.push_back(button_cap.Range.UsageMin + (USAGE) state_no);
|
usage_list.push_back(button_cap.Range.UsageMin + (USAGE) state_no);
|
||||||
@@ -2032,15 +2073,18 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
|||||||
HidP_Output,
|
HidP_Output,
|
||||||
button_cap.UsagePage,
|
button_cap.UsagePage,
|
||||||
button_cap.LinkCollection,
|
button_cap.LinkCollection,
|
||||||
&usage_list[0],
|
usage_list.data(),
|
||||||
&usage_list_length,
|
&usage_list_length,
|
||||||
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
||||||
report_data,
|
report_data.data(),
|
||||||
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
||||||
|
|
||||||
// flush report
|
// flush report
|
||||||
HidD_SetOutputReport(hid->handle, report_data, hid->caps.OutputReportByteLength);
|
HidD_SetOutputReport(
|
||||||
memset(report_data, 0, hid->caps.OutputReportByteLength);
|
hid->handle,
|
||||||
|
report_data.data(),
|
||||||
|
hid->caps.OutputReportByteLength);
|
||||||
|
std::fill(report_data.begin(), report_data.end(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear the buttons
|
// clear the buttons
|
||||||
@@ -2049,22 +2093,22 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
|||||||
HidP_Output,
|
HidP_Output,
|
||||||
button_cap.UsagePage,
|
button_cap.UsagePage,
|
||||||
button_cap.LinkCollection,
|
button_cap.LinkCollection,
|
||||||
&usage_off_list[0],
|
usage_off_list.data(),
|
||||||
&usage_off_list_length,
|
&usage_off_list_length,
|
||||||
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
||||||
report_data,
|
report_data.data(),
|
||||||
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
||||||
|
|
||||||
// flush report
|
// flush report
|
||||||
DWORD written_bytes = 0;
|
DWORD written_bytes = 0;
|
||||||
WriteFile(
|
WriteFile(
|
||||||
hid->handle,
|
hid->handle,
|
||||||
reinterpret_cast<void *>(report_data),
|
report_data.data(),
|
||||||
hid->caps.OutputReportByteLength,
|
hid->caps.OutputReportByteLength,
|
||||||
&written_bytes,
|
&written_bytes,
|
||||||
nullptr
|
nullptr
|
||||||
);
|
);
|
||||||
memset(report_data, 0, hid->caps.OutputReportByteLength);
|
std::fill(report_data.begin(), report_data.end(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2093,19 +2137,19 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
|||||||
value_cap.NotRange.Usage,
|
value_cap.NotRange.Usage,
|
||||||
static_cast<ULONG>(usage_value),
|
static_cast<ULONG>(usage_value),
|
||||||
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
||||||
report_data,
|
report_data.data(),
|
||||||
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
||||||
|
|
||||||
// flush report
|
// flush report
|
||||||
DWORD written_bytes = 0;
|
DWORD written_bytes = 0;
|
||||||
WriteFile(
|
WriteFile(
|
||||||
hid->handle,
|
hid->handle,
|
||||||
reinterpret_cast<void *>(report_data),
|
report_data.data(),
|
||||||
hid->caps.OutputReportByteLength,
|
hid->caps.OutputReportByteLength,
|
||||||
&written_bytes,
|
&written_bytes,
|
||||||
nullptr
|
nullptr
|
||||||
);
|
);
|
||||||
memset(report_data, 0, hid->caps.OutputReportByteLength);
|
std::fill(report_data.begin(), report_data.end(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2139,15 +2183,12 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
|||||||
DWORD written_bytes = 0;
|
DWORD written_bytes = 0;
|
||||||
WriteFile(
|
WriteFile(
|
||||||
hid->handle,
|
hid->handle,
|
||||||
reinterpret_cast<void *>(report_data),
|
report_data.data(),
|
||||||
hid->caps.OutputReportByteLength,
|
hid->caps.OutputReportByteLength,
|
||||||
&written_bytes,
|
&written_bytes,
|
||||||
nullptr
|
nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
// delete report
|
|
||||||
delete[] report_data;
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case HIDDriver::PacDrive: {
|
case HIDDriver::PacDrive: {
|
||||||
@@ -2208,8 +2249,6 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// unlock device
|
|
||||||
device->mutex_out->unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void rawinput::RawInputManager::devices_flush_output(bool optimized) {
|
void rawinput::RawInputManager::devices_flush_output(bool optimized) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "hotplug.h"
|
#include "hotplug.h"
|
||||||
|
#include "rawinput_handles.h"
|
||||||
#include "rawinput/xinput.h"
|
#include "rawinput/xinput.h"
|
||||||
#include "util/scope_guard.h"
|
#include "util/scope_guard.h"
|
||||||
|
|
||||||
@@ -74,6 +75,9 @@ namespace rawinput {
|
|||||||
std::list<Device> devices;
|
std::list<Device> devices;
|
||||||
std::recursive_mutex devices_mutex;
|
std::recursive_mutex devices_mutex;
|
||||||
|
|
||||||
|
// separate lookup isolated from devices_mutex for the latency-sensitive WM_INPUT path
|
||||||
|
RawInputHandles rawinput_handles;
|
||||||
|
|
||||||
WNDCLASSEX input_hwnd_class {};
|
WNDCLASSEX input_hwnd_class {};
|
||||||
std::thread *input_thread = nullptr;
|
std::thread *input_thread = nullptr;
|
||||||
std::thread *midi_thread = nullptr;
|
std::thread *midi_thread = nullptr;
|
||||||
@@ -134,10 +138,9 @@ namespace rawinput {
|
|||||||
static void CALLBACK input_midi_proc(HMIDIIN, UINT, DWORD_PTR, DWORD_PTR, DWORD_PTR);
|
static void CALLBACK input_midi_proc(HMIDIIN, UINT, DWORD_PTR, DWORD_PTR, DWORD_PTR);
|
||||||
static DeviceInfo get_device_info(const std::string &device_name);
|
static DeviceInfo get_device_info(const std::string &device_name);
|
||||||
|
|
||||||
// shared by the rawinput / xinput / midi device scans when replacing a slot
|
// replace an already-destructed slot in place, holding both device locks so
|
||||||
// in place; keeps the existing slot's mutex pair so snapshot pointers held by
|
// concurrent pollers and the output thread never see a half-copied Device
|
||||||
// other threads stay valid (see the definition in rawinput.cpp)
|
static void replace_device_slot(Device &existing, Device &replacement);
|
||||||
static void reuse_device_mutexes(Device &replacement, const Device &existing);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@@ -193,6 +196,7 @@ namespace rawinput {
|
|||||||
std::lock_guard<std::recursive_mutex> lock(devices_mutex);
|
std::lock_guard<std::recursive_mutex> lock(devices_mutex);
|
||||||
for (auto &device : devices_get()) {
|
for (auto &device : devices_get()) {
|
||||||
if (device.type == MIDI) {
|
if (device.type == MIDI) {
|
||||||
|
std::lock_guard<std::mutex> device_lock(*device.mutex);
|
||||||
device.midiInfo->freeze = freeze;
|
device.midiInfo->freeze = freeze;
|
||||||
if (!freeze) {
|
if (!freeze) {
|
||||||
for (unsigned short index = 0; index < device.midiInfo->states.size(); index++) {
|
for (unsigned short index = 0; index < device.midiInfo->states.size(); index++) {
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#include "rawinput_handles.h"
|
||||||
|
|
||||||
|
void rawinput::RawInputHandles::add(Device *device) {
|
||||||
|
std::lock_guard<std::mutex> lock(this->mutex);
|
||||||
|
this->devices[device->handle] = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rawinput::RawInputHandles::remove(Device *device) {
|
||||||
|
std::lock_guard<std::mutex> lock(this->mutex);
|
||||||
|
|
||||||
|
// teardown is shared by every device type, and handles can be reused; only erase
|
||||||
|
// an entry that still belongs to this exact RawInput device
|
||||||
|
auto it = this->devices.find(device->handle);
|
||||||
|
if (it != this->devices.end() && it->second == device) {
|
||||||
|
this->devices.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rawinput::RawInputHandles::AcquiredDevice rawinput::RawInputHandles::acquire(HANDLE handle) {
|
||||||
|
std::lock_guard<std::mutex> index_lock(this->mutex);
|
||||||
|
auto it = this->devices.find(handle);
|
||||||
|
if (it == this->devices.end()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *device = it->second;
|
||||||
|
|
||||||
|
// take the device mutex while index_lock is still held so teardown can't free it
|
||||||
|
// between the lookup and the lock
|
||||||
|
return {device, std::unique_lock<std::mutex>(*device->mutex)};
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "device.h"
|
||||||
|
|
||||||
|
namespace rawinput {
|
||||||
|
|
||||||
|
class RawInputHandles {
|
||||||
|
public:
|
||||||
|
struct AcquiredDevice {
|
||||||
|
Device *device = nullptr;
|
||||||
|
std::unique_lock<std::mutex> lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
void add(Device *device);
|
||||||
|
void remove(Device *device);
|
||||||
|
AcquiredDevice acquire(HANDLE handle);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// non-owning index; RawInputManager owns the stable device slots
|
||||||
|
std::unordered_map<HANDLE, Device *> devices;
|
||||||
|
std::mutex mutex;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -35,6 +35,8 @@ namespace rawinput::touch {
|
|||||||
long DISPLAY_NATIVE_Y = 0L;
|
long DISPLAY_NATIVE_Y = 0L;
|
||||||
bool DISPLAY_INITIALIZED = false;
|
bool DISPLAY_INITIALIZED = false;
|
||||||
|
|
||||||
|
static void update_current_display_mode();
|
||||||
|
|
||||||
bool aspect_compensation_enabled() {
|
bool aspect_compensation_enabled() {
|
||||||
switch (ASPECT_COMPENSATION_MODE) {
|
switch (ASPECT_COMPENSATION_MODE) {
|
||||||
case AspectMode::On:
|
case AspectMode::On:
|
||||||
@@ -279,7 +281,7 @@ namespace rawinput::touch {
|
|||||||
|
|
||||||
// check if display is initialized
|
// check if display is initialized
|
||||||
if (!DISPLAY_INITIALIZED) {
|
if (!DISPLAY_INITIALIZED) {
|
||||||
display_update();
|
update_current_display_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
// update timeouts here as well so events are in the right order
|
// update timeouts here as well so events are in the right order
|
||||||
@@ -317,7 +319,8 @@ namespace rawinput::touch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// iterate all input data and get touch points
|
// iterate all input data and get touch points
|
||||||
std::vector<HIDTouchPoint> touch_points;
|
thread_local std::vector<HIDTouchPoint> touch_points;
|
||||||
|
touch_points.clear();
|
||||||
touch_points.reserve(touch.elements_x.size());
|
touch_points.reserve(touch.elements_x.size());
|
||||||
for (size_t i = 0; i < touch.elements_x.size(); i++) {
|
for (size_t i = 0; i < touch.elements_x.size(); i++) {
|
||||||
|
|
||||||
@@ -422,9 +425,15 @@ namespace rawinput::touch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// process touch points
|
// process touch points
|
||||||
std::vector<DWORD> touch_removes;
|
thread_local std::vector<DWORD> touch_removes;
|
||||||
std::vector<TouchPoint> touch_writes;
|
thread_local std::vector<TouchPoint> touch_writes;
|
||||||
std::vector<DWORD> touch_modifications;
|
thread_local std::vector<DWORD> touch_modifications;
|
||||||
|
touch_removes.clear();
|
||||||
|
touch_writes.clear();
|
||||||
|
touch_modifications.clear();
|
||||||
|
touch_removes.reserve(touch.touch_points.size() + touch_points.size());
|
||||||
|
touch_writes.reserve(touch_points.size());
|
||||||
|
touch_modifications.reserve(touch_points.size());
|
||||||
|
|
||||||
// drop every touch point with the given id (marking it as released)
|
// drop every touch point with the given id (marking it as released)
|
||||||
auto remove_touch_point = [&] (DWORD id) {
|
auto remove_touch_point = [&] (DWORD id) {
|
||||||
@@ -579,7 +588,9 @@ namespace rawinput::touch {
|
|||||||
auto deadline = get_system_milliseconds() - 50;
|
auto deadline = get_system_milliseconds() - 50;
|
||||||
|
|
||||||
// check touch points
|
// check touch points
|
||||||
std::vector<DWORD> touch_removes;
|
thread_local std::vector<DWORD> touch_removes;
|
||||||
|
touch_removes.clear();
|
||||||
|
touch_removes.reserve(touch.touch_points.size());
|
||||||
auto touch_it = touch.touch_points.begin();
|
auto touch_it = touch.touch_points.begin();
|
||||||
while (touch_it != touch.touch_points.end()) {
|
while (touch_it != touch.touch_points.end()) {
|
||||||
auto &hid_tp = *touch_it;
|
auto &hid_tp = *touch_it;
|
||||||
@@ -638,12 +649,7 @@ namespace rawinput::touch {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void display_update() {
|
static void update_current_display_mode() {
|
||||||
|
|
||||||
// check if disabled
|
|
||||||
if (DISABLED)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// determine monitor size
|
// determine monitor size
|
||||||
static RECT display_rect;
|
static RECT display_rect;
|
||||||
GetWindowRect(GetDesktopWindow(), &display_rect);
|
GetWindowRect(GetDesktopWindow(), &display_rect);
|
||||||
@@ -651,12 +657,6 @@ namespace rawinput::touch {
|
|||||||
DISPLAY_SIZE_Y = display_rect.bottom - display_rect.top;
|
DISPLAY_SIZE_Y = display_rect.bottom - display_rect.top;
|
||||||
log_info("rawinput", "primary display size: {}x{}", (int) DISPLAY_SIZE_X, (int) DISPLAY_SIZE_Y);
|
log_info("rawinput", "primary display size: {}x{}", (int) DISPLAY_SIZE_X, (int) DISPLAY_SIZE_Y);
|
||||||
|
|
||||||
// determine the native (maximum) resolution of the primary display; used to
|
|
||||||
// detect and undo letterboxing when a non-native display mode is in use
|
|
||||||
detect_native_resolution();
|
|
||||||
log_info("rawinput", "primary display native size: {}x{}",
|
|
||||||
(int) DISPLAY_NATIVE_X, (int) DISPLAY_NATIVE_Y);
|
|
||||||
|
|
||||||
// determine monitor orientation
|
// determine monitor orientation
|
||||||
DEVMODE display_mode{};
|
DEVMODE display_mode{};
|
||||||
display_mode.dmSize = sizeof(DEVMODE);
|
display_mode.dmSize = sizeof(DEVMODE);
|
||||||
@@ -703,4 +703,21 @@ namespace rawinput::touch {
|
|||||||
// mark as initialized
|
// mark as initialized
|
||||||
DISPLAY_INITIALIZED = true;
|
DISPLAY_INITIALIZED = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void display_update() {
|
||||||
|
|
||||||
|
// check if disabled
|
||||||
|
if (DISABLED)
|
||||||
|
return;
|
||||||
|
|
||||||
|
update_current_display_mode();
|
||||||
|
|
||||||
|
if (aspect_compensation_enabled()) {
|
||||||
|
// determine the native (maximum) resolution of the primary display; used to
|
||||||
|
// detect and undo letterboxing when a non-native display mode is in use
|
||||||
|
detect_native_resolution();
|
||||||
|
log_info("rawinput", "primary display native size: {}x{}",
|
||||||
|
(int) DISPLAY_NATIVE_X, (int) DISPLAY_NATIVE_Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#include "reader.h"
|
#include "reader.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <cstring>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "util/precise_timer.h"
|
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "structuredmessage.h"
|
#include "structuredmessage.h"
|
||||||
|
|
||||||
@@ -218,8 +218,6 @@ bool Reader::set_comm_state(DWORD BaudRate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Reader::wait_for_handshake() {
|
bool Reader::wait_for_handshake() {
|
||||||
timeutils::PreciseSleepTimer timer;
|
|
||||||
|
|
||||||
// baud rates
|
// baud rates
|
||||||
DWORD baud_rates[] = { CBR_57600, CBR_38400, CBR_19200, CBR_9600 };
|
DWORD baud_rates[] = { CBR_57600, CBR_38400, CBR_19200, CBR_9600 };
|
||||||
|
|
||||||
@@ -270,7 +268,7 @@ bool Reader::wait_for_handshake() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sleep
|
// sleep
|
||||||
timer.sleep(50);
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
}
|
}
|
||||||
|
|
||||||
log_warning("reader", "{}: no handshake received for {} baud", this->port, baud_rates[i]);
|
log_warning("reader", "{}: no handshake received for {} baud", this->port, baud_rates[i]);
|
||||||
@@ -407,7 +405,6 @@ void start_reader_thread(const std::string &port, int id) {
|
|||||||
READER_THREAD_RUNNING = true;
|
READER_THREAD_RUNNING = true;
|
||||||
READER_THREADS.push_back(new std::thread([port, id]() {
|
READER_THREADS.push_back(new std::thread([port, id]() {
|
||||||
log_info("reader", "{}: starting reader thread", port);
|
log_info("reader", "{}: starting reader thread", port);
|
||||||
timeutils::PreciseSleepTimer timer;
|
|
||||||
|
|
||||||
while (READER_THREAD_RUNNING) {
|
while (READER_THREAD_RUNNING) {
|
||||||
|
|
||||||
@@ -445,22 +442,21 @@ void start_reader_thread(const std::string &port, int id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (did_read_card) {
|
if (did_read_card) {
|
||||||
timer.sleep(2500);
|
std::this_thread::sleep_for(std::chrono::milliseconds(2500));
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.sleep(20);
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sleep between reader connection retries
|
// sleep between reader connection retries
|
||||||
if (READER_THREAD_RUNNING)
|
if (READER_THREAD_RUNNING)
|
||||||
timer.sleep(5000);
|
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// wait for thread to start
|
// wait for thread to start
|
||||||
timeutils::PreciseSleepTimer timer;
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
timer.sleep(10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop_reader_thread() {
|
void stop_reader_thread() {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user