mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 14:50:41 -07:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5cabd026ae | |||
| 7ccd938f9b | |||
| 88a210e82e | |||
| 74e619df37 | |||
| 10a97b9c63 | |||
| 159043803c | |||
| bd2fbfcb67 |
@@ -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,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1924,7 +1924,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");
|
||||||
}
|
}
|
||||||
|
|||||||
+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';
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "hooks/audio/audio.h"
|
#include "hooks/audio/audio.h"
|
||||||
#include "hooks/audio/mme.h"
|
#include "hooks/audio/mme.h"
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
|
#include "misc/wintouchemu.h"
|
||||||
#include "overlay/overlay.h"
|
#include "overlay/overlay.h"
|
||||||
#include "touch/native/nativetouchhook.h"
|
#include "touch/native/nativetouchhook.h"
|
||||||
#include "util/cpuutils.h"
|
#include "util/cpuutils.h"
|
||||||
@@ -653,7 +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
|
||||||
nativetouch::hook(avs::game::DLL_INSTANCE);
|
const auto native_touch_ready = !wintouchemu::FORCE &&
|
||||||
|
nativetouch::hook(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);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "touch/touch.h"
|
#include "touch/touch.h"
|
||||||
#include "touch/native/nativetouchhook.h"
|
#include "touch/native/nativetouchhook.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"
|
||||||
@@ -40,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;
|
||||||
@@ -62,6 +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 = 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;
|
||||||
@@ -348,7 +352,16 @@ 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);
|
||||||
|
|
||||||
nativetouch::hook(avs::game::DLL_INSTANCE);
|
NATIVE_TOUCH = !wintouchemu::FORCE &&
|
||||||
|
nativetouch::hook(avs::game::DLL_INSTANCE);
|
||||||
|
if (!NATIVE_TOUCH) {
|
||||||
|
wintouchemu::FORCE = true;
|
||||||
|
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||||
|
wintouchemu::hook_title_ends(
|
||||||
|
"beatmania IIDX",
|
||||||
|
"main",
|
||||||
|
avs::game::DLL_INSTANCE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert BI2X hooks
|
// insert BI2X hooks
|
||||||
@@ -525,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();
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ namespace games::iidx {
|
|||||||
extern bool TDJ_MODE;
|
extern bool TDJ_MODE;
|
||||||
extern bool FORCE_720P;
|
extern bool FORCE_720P;
|
||||||
extern bool DISABLE_ESPEC_IO;
|
extern bool DISABLE_ESPEC_IO;
|
||||||
|
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;
|
||||||
@@ -52,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,
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
#include "poke.h"
|
#include "poke.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "windows.h"
|
#include "windows.h"
|
||||||
#include "games/io.h"
|
#include "games/io.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 "touch/native/inject.h"
|
#include "touch/native/inject.h"
|
||||||
#include "touch/touch.h"
|
#include "touch/touch.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/precise_timer.h"
|
|
||||||
|
|
||||||
namespace games::iidx::poke {
|
namespace games::iidx::poke {
|
||||||
|
|
||||||
@@ -116,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");
|
||||||
|
|
||||||
@@ -192,7 +196,7 @@ namespace games::iidx::poke {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// slow down
|
// slow down
|
||||||
timer.sleep(50);
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!touch_points.empty()) {
|
if (!touch_points.empty()) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "poke.h"
|
#include "poke.h"
|
||||||
#include "touch_mode.h"
|
#include "touch_mode.h"
|
||||||
#include "hooks/setupapihook.h"
|
#include "hooks/setupapihook.h"
|
||||||
|
#include "misc/wintouchemu.h"
|
||||||
#include "touch/native/nativetouchhook.h"
|
#include "touch/native/nativetouchhook.h"
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
|
|
||||||
@@ -41,9 +42,15 @@ namespace games::nost {
|
|||||||
setupapihook_init(avs::game::DLL_INSTANCE);
|
setupapihook_init(avs::game::DLL_INSTANCE);
|
||||||
setupapihook_add(touch_settings);
|
setupapihook_add(touch_settings);
|
||||||
|
|
||||||
nativetouch::hook(avs::game::DLL_INSTANCE);
|
const auto native_touch_ready = !wintouchemu::FORCE &&
|
||||||
if (ENABLE_TOUCH_MODE) {
|
nativetouch::hook(avs::game::DLL_INSTANCE);
|
||||||
touch_mode::enable();
|
if (!native_touch_ready) {
|
||||||
|
wintouchemu::FORCE = true;
|
||||||
|
wintouchemu::hook("nostalgia", avs::game::DLL_INSTANCE, 20);
|
||||||
|
} else {
|
||||||
|
if (ENABLE_TOUCH_MODE) {
|
||||||
|
touch_mode::enable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "poke.h"
|
#include "poke.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
@@ -9,8 +10,8 @@
|
|||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "touch/native/inject.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 {
|
||||||
|
|
||||||
@@ -59,11 +60,14 @@ 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 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;
|
||||||
|
|
||||||
@@ -188,7 +192,7 @@ namespace games::nost::poke {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// slow down
|
// slow down
|
||||||
timer.sleep(30);
|
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contact_active) {
|
if (contact_active) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#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"
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
namespace games::popn {
|
namespace games::popn {
|
||||||
|
|
||||||
bool SHOW_PIKA_MONITOR_WARNING = false;
|
bool SHOW_PIKA_MONITOR_WARNING = false;
|
||||||
|
bool NATIVE_TOUCH = true;
|
||||||
|
|
||||||
#if SPICE64 && !SPICE_XP
|
#if SPICE64 && !SPICE_XP
|
||||||
|
|
||||||
@@ -713,7 +715,18 @@ 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
|
||||||
|
|
||||||
nativetouch::hook(avs::game::DLL_INSTANCE);
|
NATIVE_TOUCH = !wintouchemu::FORCE &&
|
||||||
|
nativetouch::hook(avs::game::DLL_INSTANCE);
|
||||||
|
if (!NATIVE_TOUCH) {
|
||||||
|
wintouchemu::FORCE = true;
|
||||||
|
if (!GRAPHICS_WINDOWED || GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||||
|
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||||
|
wintouchemu::hook_title_ends(
|
||||||
|
"",
|
||||||
|
"Main Screen",
|
||||||
|
avs::game::DLL_INSTANCE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sysutils::hook_EnumDisplayDevicesA();
|
sysutils::hook_EnumDisplayDevicesA();
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace games::popn {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern bool SHOW_PIKA_MONITOR_WARNING;
|
extern bool SHOW_PIKA_MONITOR_WARNING;
|
||||||
|
extern bool NATIVE_TOUCH;
|
||||||
|
|
||||||
class POPNGame : public games::Game {
|
class POPNGame : public games::Game {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#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/wintouchemu.h"
|
||||||
#include "touch/native/nativetouchhook.h"
|
#include "touch/native/nativetouchhook.h"
|
||||||
#include "bi2x_hook.h"
|
#include "bi2x_hook.h"
|
||||||
#include "camera.h"
|
#include "camera.h"
|
||||||
@@ -453,7 +454,18 @@ namespace games::sdvx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_valkyrie_model()) {
|
if (is_valkyrie_model()) {
|
||||||
nativetouch::hook(avs::game::DLL_INSTANCE);
|
const auto native_touch_ready = !wintouchemu::FORCE &&
|
||||||
|
nativetouch::hook(avs::game::DLL_INSTANCE);
|
||||||
|
if (!native_touch_ready) {
|
||||||
|
wintouchemu::FORCE = true;
|
||||||
|
if (!GRAPHICS_WINDOWED) {
|
||||||
|
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||||
|
wintouchemu::hook_title_ends(
|
||||||
|
"SOUND VOLTEX",
|
||||||
|
"Main Screen",
|
||||||
|
avs::game::DLL_INSTANCE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// insert BI2X hooks
|
// insert BI2X hooks
|
||||||
bi2x_hook_init();
|
bi2x_hook_init();
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/fileutils.h"
|
#include "util/fileutils.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
#include "misc/wintouchemu.h"
|
||||||
#include "touch/native/inject.h"
|
#include "touch/native/inject.h"
|
||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
@@ -307,6 +308,22 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wintouchemu::INJECT_MOUSE_AS_WM_TOUCH) {
|
||||||
|
// drop mouse inputs since only wintouches should be used
|
||||||
|
switch (uMsg) {
|
||||||
|
case WM_MOUSEMOVE:
|
||||||
|
case WM_LBUTTONDOWN:
|
||||||
|
case WM_LBUTTONUP:
|
||||||
|
case WM_MBUTTONDOWN:
|
||||||
|
case WM_MBUTTONUP:
|
||||||
|
case WM_RBUTTONDOWN:
|
||||||
|
case WM_RBUTTONUP:
|
||||||
|
case WM_XBUTTONDOWN:
|
||||||
|
case WM_XBUTTONUP:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// window resize
|
// window resize
|
||||||
graphics_windowed_wndproc(hWnd, uMsg, wParam, lParam);
|
graphics_windowed_wndproc(hWnd, uMsg, wParam, lParam);
|
||||||
|
|
||||||
@@ -786,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;
|
||||||
|
|
||||||
nativetouch::inject::register_and_attach_window(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(
|
||||||
@@ -1160,8 +1181,9 @@ void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParamet
|
|||||||
SetWindowLongPtrA(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WindowProc));
|
SetWindowLongPtrA(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WindowProc));
|
||||||
|
|
||||||
const bool native_touch_overlay =
|
const bool native_touch_overlay =
|
||||||
(games::iidx::TDJ_MODE && !GRAPHICS_IIDX_WSUB) ||
|
(games::iidx::NATIVE_TOUCH && games::iidx::TDJ_MODE && !GRAPHICS_IIDX_WSUB) ||
|
||||||
(games::popn::is_pikapika_model() && GRAPHICS_PREVENT_SECONDARY_WINDOWS);
|
(games::popn::NATIVE_TOUCH &&
|
||||||
|
games::popn::is_pikapika_model() && GRAPHICS_PREVENT_SECONDARY_WINDOWS);
|
||||||
if (native_touch_overlay) {
|
if (native_touch_overlay) {
|
||||||
nativetouch::inject::register_and_attach_window(hWnd);
|
nativetouch::inject::register_and_attach_window(hWnd);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -1783,7 +1783,7 @@ 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;
|
||||||
|
|||||||
@@ -1791,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",
|
||||||
},
|
},
|
||||||
|
|||||||
+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));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,43 @@
|
|||||||
|
|
||||||
#include "wintouchemu.h"
|
#include "wintouchemu.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <optional>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "cfg/screen_resize.h"
|
||||||
|
#include "games/gitadora/gitadora.h"
|
||||||
|
#include "games/iidx/iidx.h"
|
||||||
|
#include "games/popn/popn.h"
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
#include "overlay/overlay.h"
|
#include "overlay/overlay.h"
|
||||||
|
#include "overlay/windows/generic_sub.h"
|
||||||
#include "touch/touch.h"
|
#include "touch/touch.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/time.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
#include "rawinput/touch.h"
|
||||||
|
|
||||||
|
#include "avs/game.h"
|
||||||
|
|
||||||
namespace wintouchemu {
|
namespace wintouchemu {
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
POINT pos;
|
||||||
|
bool last_button_pressed;
|
||||||
|
DWORD touch_event;
|
||||||
|
} mouse_state_t;
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
bool FORCE = false;
|
bool FORCE = false;
|
||||||
|
bool INJECT_MOUSE_AS_WM_TOUCH = false;
|
||||||
|
bool LOG_FPS = false;
|
||||||
bool ADD_TOUCH_FLAG_PRIMARY = false;
|
bool ADD_TOUCH_FLAG_PRIMARY = false;
|
||||||
|
|
||||||
|
// state
|
||||||
|
double last_touch_event = 0.0;
|
||||||
|
|
||||||
static inline bool is_emu_enabled() {
|
static inline bool is_emu_enabled() {
|
||||||
return FORCE || !is_touch_available("wintouchemu::is_emu_enabled") || GRAPHICS_SHOW_CURSOR;
|
return FORCE || !is_touch_available("wintouchemu::is_emu_enabled") || GRAPHICS_SHOW_CURSOR;
|
||||||
@@ -56,13 +81,16 @@ namespace wintouchemu {
|
|||||||
|
|
||||||
// state
|
// state
|
||||||
BOOL (WINAPI *GetTouchInputInfo_orig)(HANDLE, UINT, PTOUCHINPUT, int);
|
BOOL (WINAPI *GetTouchInputInfo_orig)(HANDLE, UINT, PTOUCHINPUT, int);
|
||||||
|
bool USE_MOUSE = false;
|
||||||
std::vector<TouchEvent> TOUCH_EVENTS;
|
std::vector<TouchEvent> TOUCH_EVENTS;
|
||||||
std::vector<TouchPoint> TOUCH_POINTS;
|
std::vector<TouchPoint> TOUCH_POINTS;
|
||||||
HMODULE HOOKED_MODULE = nullptr;
|
HMODULE HOOKED_MODULE = nullptr;
|
||||||
std::string WINDOW_TITLE_START = "";
|
std::string WINDOW_TITLE_START = "";
|
||||||
bool INITIALIZED = false;
|
std::optional<std::string> WINDOW_TITLE_END = std::nullopt;
|
||||||
|
volatile bool INITIALIZED = false;
|
||||||
|
mouse_state_t mouse_state;
|
||||||
|
|
||||||
void hook(const char *window_title, HMODULE module) {
|
void hook(const char *window_title, HMODULE module, int delay_in_s) {
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
auto system_metrics_hook = detour::iat_try(
|
auto system_metrics_hook = detour::iat_try(
|
||||||
@@ -81,8 +109,31 @@ namespace wintouchemu {
|
|||||||
// set module and title
|
// set module and title
|
||||||
HOOKED_MODULE = module;
|
HOOKED_MODULE = module;
|
||||||
WINDOW_TITLE_START = window_title;
|
WINDOW_TITLE_START = window_title;
|
||||||
log_misc("wintouchemu", "initializing");
|
|
||||||
INITIALIZED = true;
|
if (0 < delay_in_s) {
|
||||||
|
// some games crash when touch events are injected too early during boot
|
||||||
|
std::thread t([&]() {
|
||||||
|
log_misc("wintouchemu", "defer initialization until later (with delay of {}s)", delay_in_s);
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(delay_in_s));
|
||||||
|
log_misc("wintouchemu", "initializing", delay_in_s);
|
||||||
|
INITIALIZED = true;
|
||||||
|
});
|
||||||
|
t.detach();
|
||||||
|
} else {
|
||||||
|
log_misc("wintouchemu", "initializing");
|
||||||
|
INITIALIZED = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hook_title_ends(const char *window_title_start, const char *window_title_end, HMODULE module) {
|
||||||
|
hook(window_title_start, module);
|
||||||
|
|
||||||
|
WINDOW_TITLE_END = window_title_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(HANDLE hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) {
|
static BOOL WINAPI GetTouchInputInfoHook(HANDLE hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) {
|
||||||
@@ -94,6 +145,7 @@ namespace wintouchemu {
|
|||||||
|
|
||||||
// set touch inputs
|
// set touch inputs
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
bool mouse_used = false;
|
||||||
for (UINT input = 0; input < cInputs; input++) {
|
for (UINT input = 0; input < cInputs; input++) {
|
||||||
auto *touch_input = &pInputs[input];
|
auto *touch_input = &pInputs[input];
|
||||||
|
|
||||||
@@ -126,7 +178,13 @@ namespace wintouchemu {
|
|||||||
|
|
||||||
// log_misc("wintouchemu", "touch event ({}, {})", to_string(x), to_string(y));
|
// log_misc("wintouchemu", "touch event ({}, {})", to_string(x), to_string(y));
|
||||||
|
|
||||||
if (overlay::OVERLAY) {
|
if (GRAPHICS_IIDX_WSUB) {
|
||||||
|
// touch was received on subscreen window.
|
||||||
|
RECT clientRect {};
|
||||||
|
GetClientRect(TDJ_SUBSCREEN_WINDOW, &clientRect);
|
||||||
|
x = (float) x / clientRect.right * SPICETOUCH_TOUCH_WIDTH + SPICETOUCH_TOUCH_X;
|
||||||
|
y = (float) y / clientRect.bottom * SPICETOUCH_TOUCH_HEIGHT + SPICETOUCH_TOUCH_Y;
|
||||||
|
} else if (overlay::OVERLAY) {
|
||||||
// touch was received on global coords
|
// touch was received on global coords
|
||||||
valid = overlay::OVERLAY->transform_touch_point(&x, &y);
|
valid = overlay::OVERLAY->transform_touch_point(&x, &y);
|
||||||
} else {
|
} else {
|
||||||
@@ -172,10 +230,89 @@ namespace wintouchemu {
|
|||||||
touch_input->cxContact = 0;
|
touch_input->cxContact = 0;
|
||||||
touch_input->cyContact = 0;
|
touch_input->cyContact = 0;
|
||||||
|
|
||||||
} else {
|
if (avs::game::is_model("KFC") &&
|
||||||
|
rawinput::touch::DISPLAY_INITIALIZED &&
|
||||||
|
rawinput::touch::DISPLAY_ORIENTATION == DMDO_270) {
|
||||||
|
flip_touch_points(touch_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (USE_MOUSE && !mouse_used) {
|
||||||
|
|
||||||
|
// disable further mouse inputs this call
|
||||||
|
mouse_used = true;
|
||||||
|
|
||||||
|
if (mouse_state.touch_event) {
|
||||||
|
result = true;
|
||||||
|
touch_input->x = mouse_state.pos.x;
|
||||||
|
touch_input->y = mouse_state.pos.y;
|
||||||
|
|
||||||
|
if (GRAPHICS_WINDOWED) {
|
||||||
|
touch_input->x -= SPICETOUCH_TOUCH_X;
|
||||||
|
touch_input->y -= SPICETOUCH_TOUCH_Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// log_misc(
|
||||||
|
// "wintouchemu",
|
||||||
|
// "mouse state ({}, {}) event={}",
|
||||||
|
// to_string(touch_input->x), to_string(touch_input->y), mouse_state.touch_event);
|
||||||
|
|
||||||
|
auto valid = true;
|
||||||
|
if (overlay::OVERLAY) {
|
||||||
|
valid = overlay::OVERLAY->transform_touch_point(
|
||||||
|
&touch_input->x, &touch_input->y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// touch inputs require 100x precision per pixel
|
||||||
|
touch_input->x *= 100;
|
||||||
|
touch_input->y *= 100;
|
||||||
|
touch_input->hSource = hTouchInput;
|
||||||
|
touch_input->dwID = 0;
|
||||||
|
touch_input->dwFlags = 0;
|
||||||
|
switch (mouse_state.touch_event) {
|
||||||
|
case TOUCHEVENTF_DOWN:
|
||||||
|
if (valid) {
|
||||||
|
if (ADD_TOUCH_FLAG_PRIMARY) {
|
||||||
|
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
|
||||||
|
}
|
||||||
|
|
||||||
|
touch_input->dwFlags |= TOUCHEVENTF_DOWN;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TOUCHEVENTF_MOVE:
|
||||||
|
if (valid) {
|
||||||
|
if (ADD_TOUCH_FLAG_PRIMARY) {
|
||||||
|
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
|
||||||
|
}
|
||||||
|
|
||||||
|
touch_input->dwFlags |= TOUCHEVENTF_MOVE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TOUCHEVENTF_UP:
|
||||||
|
// don't check valid so that this touch ID can be released
|
||||||
|
if (ADD_TOUCH_FLAG_PRIMARY) {
|
||||||
|
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
|
||||||
|
}
|
||||||
|
|
||||||
|
touch_input->dwFlags |= TOUCHEVENTF_UP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
touch_input->dwMask = 0;
|
||||||
|
touch_input->dwTime = 0;
|
||||||
|
touch_input->dwExtraInfo = 0;
|
||||||
|
touch_input->cxContact = 0;
|
||||||
|
touch_input->cyContact = 0;
|
||||||
|
|
||||||
|
// reset it since the event was consumed & propagated as touch
|
||||||
|
mouse_state.touch_event = 0;
|
||||||
|
}
|
||||||
|
} else if (!GRAPHICS_IIDX_WSUB) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For some reason, Nostalgia won't show an active touch point unless a move event
|
||||||
|
* triggers in the same frame. To work around this, we just supply a fake move
|
||||||
|
* event if we didn't update the same pointer ID in the same call.
|
||||||
|
*/
|
||||||
|
|
||||||
// beatstream requires a MOVE for active points in each update
|
|
||||||
// add one for every active point without a matching input event
|
|
||||||
// find touch point which has no associated input event
|
// find touch point which has no associated input event
|
||||||
TouchPoint *touch_point = nullptr;
|
TouchPoint *touch_point = nullptr;
|
||||||
for (auto &tp : TOUCH_POINTS) {
|
for (auto &tp : TOUCH_POINTS) {
|
||||||
@@ -243,6 +380,21 @@ namespace wintouchemu {
|
|||||||
title = get_window_title(hWnd);
|
title = get_window_title(hWnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if a window title end is set, check to see if it matches
|
||||||
|
if (WINDOW_TITLE_END.has_value() && !string_ends_with(title.c_str(), WINDOW_TITLE_END.value().c_str())) {
|
||||||
|
hWnd = nullptr;
|
||||||
|
title = "";
|
||||||
|
|
||||||
|
for (auto &window : find_windows_beginning_with(WINDOW_TITLE_START)) {
|
||||||
|
auto check_title = get_window_title(window);
|
||||||
|
if (string_ends_with(check_title.c_str(), WINDOW_TITLE_END.value().c_str())) {
|
||||||
|
hWnd = std::move(window);
|
||||||
|
title = std::move(check_title);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check window
|
// check window
|
||||||
if (hWnd == nullptr) {
|
if (hWnd == nullptr) {
|
||||||
return;
|
return;
|
||||||
@@ -250,13 +402,39 @@ namespace wintouchemu {
|
|||||||
|
|
||||||
// check if windowed
|
// check if windowed
|
||||||
if (GRAPHICS_WINDOWED) {
|
if (GRAPHICS_WINDOWED) {
|
||||||
// create touch window - create overlay if not yet existing at this point
|
if (GRAPHICS_IIDX_WSUB) {
|
||||||
log_info("wintouchemu", "create touch window relative to main game window");
|
// no handling is needed here
|
||||||
touch_create_wnd(hWnd, overlay::ENABLED && !overlay::OVERLAY);
|
// graphics::MoveWindow_hook will attach hook to windowed subscreen
|
||||||
|
log_info("wintouchemu", "attach touch hook to windowed subscreen for TDJ");
|
||||||
|
USE_MOUSE = false;
|
||||||
|
} else if (avs::game::is_model("LDJ") && !GENERIC_SUB_WINDOW_FULLSIZE) {
|
||||||
|
// overlay subscreen in IIDX
|
||||||
|
// use mouse position as ImGui overlay will block the touch window
|
||||||
|
log_info("wintouchemu", "use mouse cursor API for ldj overlay subscreen");
|
||||||
|
USE_MOUSE = true;
|
||||||
|
} else if (games::popn::is_pikapika_model()) {
|
||||||
|
// same as iidx case above
|
||||||
|
log_info("wintouchemu", "use mouse cursor API for popn overlay subscreen");
|
||||||
|
USE_MOUSE = true;
|
||||||
|
} else if (games::gitadora::is_arena_model() && GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||||
|
log_info("wintouchemu", "use mouse cursor API for gitadora overlay subscreen");
|
||||||
|
USE_MOUSE = true;
|
||||||
|
} else {
|
||||||
|
// create touch window - create overlay if not yet existing at this point
|
||||||
|
log_info("wintouchemu", "create touch window relative to main game window");
|
||||||
|
touch_create_wnd(hWnd, overlay::ENABLED && !overlay::OVERLAY);
|
||||||
|
USE_MOUSE = false;
|
||||||
|
}
|
||||||
|
} else if (INJECT_MOUSE_AS_WM_TOUCH) {
|
||||||
|
log_info(
|
||||||
|
"wintouchemu",
|
||||||
|
"using raw mouse cursor API in full screen and injecting them as WM_TOUCH events");
|
||||||
|
USE_MOUSE = true;
|
||||||
} else {
|
} else {
|
||||||
log_info("wintouchemu", "activating DirectX hooks");
|
log_info("wintouchemu", "activating DirectX hooks");
|
||||||
// mouse position based input only
|
// mouse position based input only
|
||||||
touch_attach_dx_hook();
|
touch_attach_dx_hook();
|
||||||
|
USE_MOUSE = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
@@ -267,6 +445,8 @@ namespace wintouchemu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto now = get_performance_milliseconds();
|
||||||
|
|
||||||
// update touch events
|
// update touch events
|
||||||
if (hWnd != nullptr) {
|
if (hWnd != nullptr) {
|
||||||
|
|
||||||
@@ -286,11 +466,64 @@ namespace wintouchemu {
|
|||||||
|
|
||||||
// check if new events are available
|
// check if new events are available
|
||||||
if (event_count > 0) {
|
if (event_count > 0) {
|
||||||
|
last_touch_event = now;
|
||||||
|
|
||||||
// send fake event to make the game update it's touch inputs
|
// send fake event to make the game update it's touch inputs
|
||||||
auto wndProc = (WNDPROC) GetWindowLongPtr(hWnd, GWLP_WNDPROC);
|
auto wndProc = (WNDPROC) GetWindowLongPtr(hWnd, GWLP_WNDPROC);
|
||||||
wndProc(hWnd, WM_TOUCH, MAKEWORD(event_count, 0), (LPARAM) GetTouchInputInfoHook);
|
wndProc(hWnd, WM_TOUCH, MAKEWORD(event_count, 0), (LPARAM) GetTouchInputInfoHook);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update frame logging
|
||||||
|
if (LOG_FPS) {
|
||||||
|
static int log_frames = 0;
|
||||||
|
static uint64_t log_time = 0;
|
||||||
|
log_frames++;
|
||||||
|
if (log_time < get_system_seconds()) {
|
||||||
|
if (log_time > 0) {
|
||||||
|
log_info("wintouchemu", "polling at {} touch frames per second", log_frames);
|
||||||
|
}
|
||||||
|
log_frames = 0;
|
||||||
|
log_time = get_system_seconds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send separate WM_TOUCH event for mouse
|
||||||
|
// this must be separate from actual touch events because some games will ignore the return
|
||||||
|
// value from GetTouchInputInfo or fail to read dwFlags for valid events, so it's not OK to
|
||||||
|
// send empty events when the mouse button is not clicked/released
|
||||||
|
if (hWnd != nullptr && USE_MOUSE) {
|
||||||
|
bool button_pressed = get_async_primary_mouse();
|
||||||
|
|
||||||
|
// while the subscreen overlay cannot accept the mouse, treat the button as
|
||||||
|
// released; this ends any active touch and prevents new mouse touch contacts
|
||||||
|
if (overlay::OVERLAY &&
|
||||||
|
overlay::OVERLAY->has_subscreen_touch_transform() &&
|
||||||
|
!overlay::OVERLAY->accepts_subscreen_mouse_input()) {
|
||||||
|
button_pressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there was a touch event in the last 500 ms, don't insert new button presses
|
||||||
|
if (button_pressed && (now - last_touch_event) < 500) {
|
||||||
|
button_pressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out what kind of touch event to simulate
|
||||||
|
if (button_pressed && !mouse_state.last_button_pressed) {
|
||||||
|
mouse_state.touch_event = TOUCHEVENTF_DOWN;
|
||||||
|
} else if (button_pressed && mouse_state.last_button_pressed) {
|
||||||
|
mouse_state.touch_event = TOUCHEVENTF_MOVE;
|
||||||
|
} else if (!button_pressed && mouse_state.last_button_pressed) {
|
||||||
|
mouse_state.touch_event = TOUCHEVENTF_UP;
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse_state.last_button_pressed = button_pressed;
|
||||||
|
if (mouse_state.touch_event) {
|
||||||
|
GetCursorPos(&mouse_state.pos);
|
||||||
|
// send fake event to make the game update it's touch inputs
|
||||||
|
auto wndProc = (WNDPROC) GetWindowLongPtr(hWnd, GWLP_WNDPROC);
|
||||||
|
wndProc(hWnd, WM_TOUCH, MAKEWORD(1, 0), (LPARAM) GetTouchInputInfoHook);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,14 @@ namespace wintouchemu {
|
|||||||
|
|
||||||
// settings
|
// settings
|
||||||
extern bool FORCE;
|
extern bool FORCE;
|
||||||
|
extern bool INJECT_MOUSE_AS_WM_TOUCH;
|
||||||
|
extern bool LOG_FPS;
|
||||||
extern bool ADD_TOUCH_FLAG_PRIMARY;
|
extern bool ADD_TOUCH_FLAG_PRIMARY;
|
||||||
|
|
||||||
void hook(const char *window_title, HMODULE module = nullptr);
|
void hook(const char *window_title, HMODULE module = nullptr, int delay_in_s=0);
|
||||||
|
void hook_title_ends(
|
||||||
|
const char *window_title_start,
|
||||||
|
const char *window_title_ends,
|
||||||
|
HMODULE module = nullptr);
|
||||||
void update();
|
void update();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -908,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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,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();
|
||||||
|
|
||||||
@@ -143,20 +144,20 @@ namespace overlay {
|
|||||||
return this->hWnd;
|
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
|
||||||
@@ -203,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;
|
||||||
@@ -216,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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
@@ -169,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,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];
|
||||||
|
|
||||||
@@ -458,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,
|
||||||
@@ -467,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++) {
|
||||||
@@ -512,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,
|
||||||
@@ -800,8 +823,18 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
|||||||
new_device.hidInfo->button_states = std::move(button_states);
|
new_device.hidInfo->button_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);
|
||||||
@@ -1573,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;
|
||||||
@@ -1791,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()),
|
||||||
@@ -1819,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) {
|
||||||
@@ -1852,17 +1880,18 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
|||||||
|
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2017,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++) {
|
||||||
@@ -2026,10 +2057,8 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
|||||||
auto &button_state_list = hid->button_output_states[cap_no];
|
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);
|
||||||
@@ -2044,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
|
||||||
@@ -2061,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2105,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2151,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: {
|
||||||
|
|||||||
@@ -319,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++) {
|
||||||
|
|
||||||
@@ -424,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) {
|
||||||
@@ -581,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;
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -338,9 +338,7 @@ namespace nativetouch::inject {
|
|||||||
|
|
||||||
// attach mouse injection without replacing the window's existing procedure
|
// attach mouse injection without replacing the window's existing procedure
|
||||||
static void attach_window_impl(HWND window, bool register_touch) {
|
static void attach_window_impl(HWND window, bool register_touch) {
|
||||||
initialize_touch_injection();
|
if (!initialize_touch_injection()) {
|
||||||
|
|
||||||
if (!touch_injection_available()) {
|
|
||||||
if (register_touch) {
|
if (register_touch) {
|
||||||
log_warning(
|
log_warning(
|
||||||
"touch::native",
|
"touch::native",
|
||||||
@@ -422,7 +420,7 @@ namespace nativetouch::inject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// load and initialize Windows 8 touch injection without a static API dependency
|
// load and initialize Windows 8 touch injection without a static API dependency
|
||||||
void initialize_touch_injection() {
|
bool initialize_touch_injection() {
|
||||||
std::call_once(initialization_once, [] {
|
std::call_once(initialization_once, [] {
|
||||||
initialize_synthetic_touch();
|
initialize_synthetic_touch();
|
||||||
contact_refresh_message =
|
contact_refresh_message =
|
||||||
@@ -433,17 +431,26 @@ namespace nativetouch::inject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// load all APIs from user32 without adding static imports
|
// load all APIs from user32 without adding static imports
|
||||||
|
//
|
||||||
|
// note that these are expected to be present in Windows 8 and above;
|
||||||
|
// however, on WINE, touch implementation remains in Windows 7 era
|
||||||
|
// and therefore the hooks below will fail, hence the fallback to
|
||||||
|
// legacy wintouchemu code
|
||||||
const auto user32 = libutils::load_library("user32.dll");
|
const auto user32 = libutils::load_library("user32.dll");
|
||||||
InitializeTouchInjection_ptr = libutils::try_proc<decltype(InitializeTouchInjection_ptr)>(
|
InitializeTouchInjection_ptr = libutils::try_proc<decltype(InitializeTouchInjection_ptr)>(
|
||||||
user32, "InitializeTouchInjection");
|
user32, "InitializeTouchInjection");
|
||||||
|
if (InitializeTouchInjection_ptr == nullptr) {
|
||||||
|
log_warning(
|
||||||
|
"touch::native", "InitializeTouchInjection unavailable; mouse touch injection disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
InjectTouchInput_ptr = libutils::try_proc<decltype(InjectTouchInput_ptr)>(
|
InjectTouchInput_ptr = libutils::try_proc<decltype(InjectTouchInput_ptr)>(
|
||||||
user32, "InjectTouchInput");
|
user32, "InjectTouchInput");
|
||||||
|
if (InjectTouchInput_ptr == nullptr) {
|
||||||
if (InitializeTouchInjection_ptr == nullptr ||
|
|
||||||
InjectTouchInput_ptr == nullptr) {
|
|
||||||
clear_touch_injection_functions();
|
clear_touch_injection_functions();
|
||||||
log_warning(
|
log_warning(
|
||||||
"touch::native", "touch injection API unavailable; mouse touch injection disabled");
|
"touch::native", "InjectTouchInput unavailable; mouse touch injection disabled");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,20 +464,31 @@ namespace nativetouch::inject {
|
|||||||
|
|
||||||
log_misc("touch::native", "mouse touch injection initialized");
|
log_misc("touch::native", "mouse touch injection initialized");
|
||||||
});
|
});
|
||||||
}
|
return InitializeTouchInjection_ptr != nullptr && InjectTouchInput_ptr != nullptr;
|
||||||
|
|
||||||
bool touch_injection_available() {
|
|
||||||
return InjectTouchInput_ptr != nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// install injection support for touch windows registered by the game module
|
// install injection support for touch windows registered by the game module
|
||||||
void hook(HMODULE module) {
|
bool hook_available(HMODULE module) {
|
||||||
initialize_touch_injection();
|
if (detour::iat_find("RegisterTouchWindow", module) == nullptr) {
|
||||||
|
log_warning("touch::native", "RegisterTouchWindow unavailable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hook(HMODULE module) {
|
||||||
|
if (!initialize_touch_injection()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
RegisterTouchWindow_orig = detour::iat_try(
|
RegisterTouchWindow_orig = detour::iat_try(
|
||||||
"RegisterTouchWindow", RegisterTouchWindowHook, module);
|
"RegisterTouchWindow", RegisterTouchWindowHook, module);
|
||||||
if (RegisterTouchWindow_orig != nullptr) {
|
if (RegisterTouchWindow_orig == nullptr) {
|
||||||
log_misc("touch::native", "RegisterTouchWindow hooked");
|
log_warning("touch::native", "failed to hook RegisterTouchWindow");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log_misc("touch::native", "RegisterTouchWindow hooked");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ struct tagTOUCHINPUT;
|
|||||||
namespace nativetouch::inject {
|
namespace nativetouch::inject {
|
||||||
void attach_window(HWND window);
|
void attach_window(HWND window);
|
||||||
void register_and_attach_window(HWND window);
|
void register_and_attach_window(HWND window);
|
||||||
void hook(HMODULE module);
|
bool hook_available(HMODULE module);
|
||||||
|
bool hook(HMODULE module);
|
||||||
bool inject_synthetic_touch(POINT position, bool down);
|
bool inject_synthetic_touch(POINT position, bool down);
|
||||||
bool inject_synthetic_touch_from_canvas(POINT position, SIZE canvas, bool down);
|
bool inject_synthetic_touch_from_canvas(POINT position, SIZE canvas, bool down);
|
||||||
bool transform_touch_input(tagTOUCHINPUT *point);
|
bool transform_touch_input(tagTOUCHINPUT *point);
|
||||||
|
|||||||
@@ -10,9 +10,8 @@ namespace nativetouch::inject {
|
|||||||
Synthetic,
|
Synthetic,
|
||||||
};
|
};
|
||||||
|
|
||||||
void initialize_touch_injection();
|
bool initialize_touch_injection();
|
||||||
void initialize_synthetic_touch();
|
void initialize_synthetic_touch();
|
||||||
bool touch_injection_available();
|
|
||||||
void refresh_contact_lifetime();
|
void refresh_contact_lifetime();
|
||||||
|
|
||||||
bool contact_is_active();
|
bool contact_is_active();
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace nativetouch::inject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
POINT transformed = *position;
|
POINT transformed = *position;
|
||||||
return transform::screen_to_game(window, &transformed);
|
return transform::mouse_to_game(window, &transformed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// release the active injected contact and its window capture
|
// release the active injected contact and its window capture
|
||||||
@@ -108,7 +108,7 @@ namespace nativetouch::inject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
POINT transformed = position;
|
POINT transformed = position;
|
||||||
if (!transform::screen_to_game(window, &transformed)) {
|
if (!transform::mouse_to_game(window, &transformed)) {
|
||||||
end_mouse_contact(window);
|
end_mouse_contact(window);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,11 +113,12 @@ namespace nativetouch::inject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static HWND prepare_synthetic_touch() {
|
static HWND prepare_synthetic_touch() {
|
||||||
initialize_touch_injection();
|
if (!initialize_touch_injection()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
const auto window = get_injection_window();
|
const auto window = get_injection_window();
|
||||||
if (!touch_injection_available() || window == nullptr ||
|
if (window == nullptr || synthetic_touch_message == 0) {
|
||||||
synthetic_touch_message == 0) {
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return window;
|
return window;
|
||||||
|
|||||||
@@ -223,25 +223,58 @@ namespace nativetouch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void hook(HMODULE module) {
|
static bool hook_prerequisites_available(HMODULE module) {
|
||||||
|
if (detour::iat_find("GetTouchInputInfo", module) == nullptr) {
|
||||||
|
log_warning("touch::native", "GetTouchInputInfo unavailable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (settings::EMULATE_DIGITIZER &&
|
||||||
|
detour::iat_find("GetSystemMetrics", module) == nullptr) {
|
||||||
|
log_warning("touch::native", "GetSystemMetrics unavailable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hook(HMODULE module) {
|
||||||
|
native_touch_hooked = false;
|
||||||
initialize_game_settings();
|
initialize_game_settings();
|
||||||
|
|
||||||
inject::hook(module);
|
// check if the OS supports touch API (Win7+) and injection API (requires Win8+)
|
||||||
|
if (!hook_prerequisites_available(module)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!inject::hook_available(module)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
native_touch_hooked = true;
|
// try hooking injection API first since they require the highest OS level
|
||||||
|
// (WINE specifically did not implement this in 2026)
|
||||||
|
if (!inject::hook(module)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSystemMetrics
|
||||||
if (settings::EMULATE_DIGITIZER) {
|
if (settings::EMULATE_DIGITIZER) {
|
||||||
GetSystemMetrics_orig = detour::iat_try(
|
GetSystemMetrics_orig = detour::iat_try(
|
||||||
"GetSystemMetrics", GetSystemMetricsHook, module);
|
"GetSystemMetrics", GetSystemMetricsHook, module);
|
||||||
if (GetSystemMetrics_orig != nullptr) {
|
if (GetSystemMetrics_orig == nullptr) {
|
||||||
log_misc("touch::native", "GetSystemMetrics hooked");
|
log_warning("touch::native", "failed to hook GetSystemMetrics");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
log_misc("touch::native", "GetSystemMetrics hooked");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTouchInputInfo
|
||||||
GetTouchInputInfo_orig = detour::iat_try("GetTouchInputInfo", GetTouchInputInfoHook, module);
|
GetTouchInputInfo_orig = detour::iat_try("GetTouchInputInfo", GetTouchInputInfoHook, module);
|
||||||
if (GetTouchInputInfo_orig != nullptr) {
|
if (GetTouchInputInfo_orig == nullptr) {
|
||||||
log_misc("touch::native", "GetTouchInputInfo hooked");
|
log_warning("touch::native", "failed to hook GetTouchInputInfo");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
log_misc("touch::native", "GetTouchInputInfo hooked");
|
||||||
|
|
||||||
|
native_touch_hooked = true;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace nativetouch {
|
|||||||
|
|
||||||
using TouchInputFilter = bool (*)(const NativeTouchEvent &event);
|
using TouchInputFilter = bool (*)(const NativeTouchEvent &event);
|
||||||
|
|
||||||
void hook(HMODULE module);
|
bool hook(HMODULE module);
|
||||||
void refresh_contact_lifetime();
|
void refresh_contact_lifetime();
|
||||||
void set_input_filter(TouchInputFilter filter);
|
void set_input_filter(TouchInputFilter filter);
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ namespace nativetouch::transform {
|
|||||||
static bool has_active_overlay_transform() {
|
static bool has_active_overlay_transform() {
|
||||||
return overlay::OVERLAY != nullptr &&
|
return overlay::OVERLAY != nullptr &&
|
||||||
overlay::OVERLAY->get_active() &&
|
overlay::OVERLAY->get_active() &&
|
||||||
overlay::OVERLAY->can_transform_touch_input();
|
overlay::OVERLAY->has_subscreen_touch_transform();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool transform_overlay_touch_position(POINT *position) {
|
static bool transform_overlay_touch_position(POINT *position) {
|
||||||
@@ -120,6 +120,25 @@ namespace nativetouch::transform {
|
|||||||
return transform_overlay_touch_position(position);
|
return transform_overlay_touch_position(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool mouse_to_game(HWND window, POINT *position) {
|
||||||
|
|
||||||
|
// exception: iidx tdj dedicated subscreen window is allowed
|
||||||
|
if (is_tdj_dedicated_subscreen(window)) {
|
||||||
|
return screen_to_game(window, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this game has a subscreen overlay that can transform touch input
|
||||||
|
// but the window is hidden or not under the cursor, reject mouse-as-touch
|
||||||
|
// (e.g., iidx/sdvx are rejected here, but nostalgia is allowed)
|
||||||
|
if (overlay::OVERLAY != nullptr &&
|
||||||
|
overlay::OVERLAY->has_subscreen_touch_transform() &&
|
||||||
|
!overlay::OVERLAY->accepts_subscreen_mouse_input()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return screen_to_game(window, position);
|
||||||
|
}
|
||||||
|
|
||||||
// route hardware screen coordinates through dedicated or overlay mapping and report the result
|
// route hardware screen coordinates through dedicated or overlay mapping and report the result
|
||||||
Result hardware_to_game(POINT *position) {
|
Result hardware_to_game(POINT *position) {
|
||||||
const auto dedicated_subscreen = is_tdj_dedicated_subscreen(TDJ_SUBSCREEN_WINDOW);
|
const auto dedicated_subscreen = is_tdj_dedicated_subscreen(TDJ_SUBSCREEN_WINDOW);
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ namespace nativetouch::transform {
|
|||||||
bool is_tdj_dedicated_subscreen(HWND window);
|
bool is_tdj_dedicated_subscreen(HWND window);
|
||||||
bool game_to_screen(HWND window, POINT *position);
|
bool game_to_screen(HWND window, POINT *position);
|
||||||
bool screen_to_game(HWND window, POINT *position);
|
bool screen_to_game(HWND window, POINT *position);
|
||||||
|
bool mouse_to_game(HWND window, POINT *position);
|
||||||
Result hardware_to_game(POINT *position);
|
Result hardware_to_game(POINT *position);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -944,7 +944,7 @@ void touch_get_points(std::vector<TouchPoint> &touch_points, bool overlay) {
|
|||||||
if (!overlay &&
|
if (!overlay &&
|
||||||
overlay::OVERLAY &&
|
overlay::OVERLAY &&
|
||||||
overlay::OVERLAY->get_active() &&
|
overlay::OVERLAY->get_active() &&
|
||||||
!overlay::OVERLAY->can_transform_touch_input() &&
|
!overlay::OVERLAY->has_subscreen_touch_transform() &&
|
||||||
ImGui::GetIO().WantCaptureMouse) {
|
ImGui::GetIO().WantCaptureMouse) {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -971,7 +971,7 @@ void touch_get_events(std::vector<TouchEvent> &touch_events, bool overlay) {
|
|||||||
if (!overlay &&
|
if (!overlay &&
|
||||||
overlay::OVERLAY &&
|
overlay::OVERLAY &&
|
||||||
overlay::OVERLAY->get_active() &&
|
overlay::OVERLAY->get_active() &&
|
||||||
!overlay::OVERLAY->can_transform_touch_input() &&
|
!overlay::OVERLAY->has_subscreen_touch_transform() &&
|
||||||
ImGui::GetIO().WantCaptureMouse) {
|
ImGui::GetIO().WantCaptureMouse) {
|
||||||
|
|
||||||
TOUCH_EVENTS.reset();
|
TOUCH_EVENTS.reset();
|
||||||
|
|||||||
Reference in New Issue
Block a user