Compare commits

...

7 Commits

Author SHA1 Message Date
bicarus 5cabd026ae misc: various performance clean up (#844)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change

- Replace unnecessary precision timers with standard sleeps to reduce
wakeups and background CPU usage.
- Reuse buffers in raw input, touchscreen, and HID output paths to
eliminate steady-state allocations.
- Pre-index HID button groups and correctly process batched HID reports.

No functional changes.

## Testing
2026-07-28 21:05:32 -07:00
bicarus 7ccd938f9b overlay: mouse-as-touch needs to check if subscreen window is active (#842)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #840 

## Description of change
Currently, for subscreen games, mouse events are still delivered even
when the subscreen overlay window is not visible.

Change this so that the subscreen overlay must be active and under the
mouse cursor for the mouse-to-touch transformation to occur. Applies to
both native and wintouchemu.

## Testing
Needs to test everything again..

iidx:

- [ ]  full screen with overlay
- [ ]  single window with overlay
- [ ]  two window

Test: mouse, poke, api, real touch screen

sdvx:

- [ ] full screen with overlay
- [ ] windowed

popn

- [x] full screen with overlay
- [x] window with overlay
- [x] dedicated window

gitadora

- [x] single window with overlay
- [x] dedicated sub window

nostalgia

- [x] fullscreen
- [x] windowed

test: poke

wintouchemu
- [ ] Do all of the above again with wintouchemu
2026-07-28 01:14:47 -07:00
bicarus 88a210e82e iidx: camera hook improvements (#841)
## Link to GitHub Issue or related Pull Request, if one exists
#201

## Description of change

- Adds MJPEG camera support using Media Foundation decoding.
- Moves camera capture to asynchronous Source Reader callbacks.
- Supports native NV12 and YUY2 capture, preferring target-sized native
formats over MJPEG when possible.
- Improves capture and rendering performance through callback-paced
reads, bulk frame uploads, optimized row-based flips, and lazy
allocation of flip textures.
- Makes runtime media-type changes interrupt pending reads and safely
flush and reconfigure the Source Reader.
- Improves automatic mode selection based on aspect ratio, proximity to
1280x720, frame rate, and capture format.
- Hardens camera shutdown, flush handling, media-type changes, and
repeated capture failures.

## Testing
Tested with two cameras in tdj
2026-07-27 08:42:46 -07:00
bicarus 74e619df37 sdvx: fix check for enable_console in avs_config.xml (#838)
## Link to GitHub Issue or related Pull Request, if one exists
Regressed by #657 

## Description of change
Workaround for SDVX4 was applied too broadly and caused sdvx 1/2/3 to
not boot, depending on contents of avs-config.xml.

`property_search_safe` throws a fatal error if the node is not present.
What we wanted to do was to check for presence.

## Testing
sanity checked sdvx 1/2/3/4
2026-07-26 16:16:25 -07:00
bicarus 10a97b9c63 signal: dump exception context, log dump creation failure (#837)
More diagnostics info for game crashes.
2026-07-26 15:25:24 -07:00
bicarus 159043803c imgui: fix crash when launching from UNC path (network shares) (#836)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
ImGui filebrowser extension crashes due to MinGW quirk about UNC path
handling.

## Testing
2026-07-26 04:30:06 -07:00
bicarus bd2fbfcb67 touch: restore wintouchemu, fall back to it when native touch hooks fail (#834)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #833

## Description of change
Last couple PRs - such as #820 #827 #828 - made the native touch hook &
touch injection using `InjectTouchInput` the default path, since it
performs much more reliably with both real touch screens and mouse (or
any other synthetic source).

However, user has reported that WINE lacks `InjectTouchInput` which
means this won't work.

As a fix, revive the old wintouchemu code. Native touch is still the
default, but under following circumstances:

1. if `-touchemuforce` is set, or
2. if any of the required Windows touch APIs are unavailable

then we fail over from native touch to wintouchemu code. 

For Linux, condition #2 would be hit during init, and gracefully switch
over.

Caveat: the poke code for IIDX and Nost will continue to require native
touch, I do not want to maintain two paths for this. This means that
iidx poke will stop working on Linux, unfortunately.

## Testing
Tested on Windows with `-touchemuforce` set. This is mostly reverting
Linux code path back to where we were last release, so this should just
work with wine.
2026-07-26 04:29:45 -07:00
46 changed files with 1963 additions and 435 deletions
+4 -4
View File
@@ -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));
} }
} }
+3 -3
View File
@@ -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));
} }
} }
}); });
+1 -1
View File
@@ -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
View File
@@ -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';
+8 -1
View File
@@ -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
+10 -8
View File
@@ -534,7 +534,7 @@ namespace games::iidx {
if (mediaTypePointer && mediaTypePointer->IsString()) { if (mediaTypePointer && mediaTypePointer->IsString()) {
std::string mediaType = mediaTypePointer->GetString(); std::string mediaType = mediaTypePointer->GetString();
if (mediaType.length() > 0) { if (mediaType.length() > 0) {
camera->m_selectedMediaTypeDescription = mediaType; camera->SetSelectedMediaTypeDescription(mediaType);
camera->m_useAutoMediaType = false; camera->m_useAutoMediaType = false;
} else { } else {
camera->m_useAutoMediaType = true; camera->m_useAutoMediaType = true;
@@ -547,7 +547,7 @@ namespace games::iidx {
std::string drawModeString = drawModePointer->GetString(); std::string drawModeString = drawModePointer->GetString();
for (int j = 0; j < DRAW_MODE_SIZE; j++) { for (int j = 0; j < DRAW_MODE_SIZE; j++) {
if (DRAW_MODE_LABELS[j].compare(drawModeString) == 0) { if (DRAW_MODE_LABELS[j].compare(drawModeString) == 0) {
camera->m_drawMode = (LocalCameraDrawMode) j; camera->m_drawMode.store((LocalCameraDrawMode) j);
break; break;
} }
} }
@@ -556,12 +556,12 @@ namespace games::iidx {
// Flip // Flip
auto flipHorizontalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipHorizontal").Get(doc); auto flipHorizontalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipHorizontal").Get(doc);
if (flipHorizontalPointer && flipHorizontalPointer->IsBool()) { if (flipHorizontalPointer && flipHorizontalPointer->IsBool()) {
camera->m_flipHorizontal = flipHorizontalPointer->GetBool(); camera->m_flipHorizontal.store(flipHorizontalPointer->GetBool());
} }
auto flipVerticalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipVertical").Get(doc); auto flipVerticalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipVertical").Get(doc);
if (flipVerticalPointer && flipVerticalPointer->IsBool()) { if (flipVerticalPointer && flipVerticalPointer->IsBool()) {
camera->m_flipVertical = flipVerticalPointer->GetBool(); camera->m_flipVertical.store(flipVerticalPointer->GetBool());
} }
// Allow manual control // Allow manual control
@@ -625,15 +625,17 @@ namespace games::iidx {
if (camera->m_useAutoMediaType) { if (camera->m_useAutoMediaType) {
rapidjson::Pointer(root + "MediaType").Set(doc, ""); rapidjson::Pointer(root + "MediaType").Set(doc, "");
} else { } else {
rapidjson::Pointer(root + "MediaType").Set(doc, camera->m_selectedMediaTypeDescription); const auto mediaTypeDescription = camera->GetSelectedMediaTypeDescription();
rapidjson::Pointer(root + "MediaType").Set(doc, mediaTypeDescription);
} }
// Draw Mode // Draw Mode
rapidjson::Pointer(root + "DrawMode").Set(doc, DRAW_MODE_LABELS[camera->m_drawMode]); const auto drawMode = camera->m_drawMode.load();
rapidjson::Pointer(root + "DrawMode").Set(doc, DRAW_MODE_LABELS[drawMode]);
// Flip // Flip
rapidjson::Pointer(root + "FlipHorizontal").Set(doc, camera->m_flipHorizontal); rapidjson::Pointer(root + "FlipHorizontal").Set(doc, camera->m_flipHorizontal.load());
rapidjson::Pointer(root + "FlipVertical").Set(doc, camera->m_flipVertical); rapidjson::Pointer(root + "FlipVertical").Set(doc, camera->m_flipVertical.load());
// Manual control // Manual control
rapidjson::Pointer(root + "AllowManualControl").Set(doc, camera->m_allowManualControl); rapidjson::Pointer(root + "AllowManualControl").Set(doc, camera->m_allowManualControl);
+20 -1
View File
@@ -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();
+3
View File
@@ -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
+36 -11
View File
@@ -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 &current);
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();
}; };
} }
+16
View File
@@ -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,
+3
View File
@@ -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,
+8 -4
View File
@@ -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()) {
+10 -3
View File
@@ -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();
}
} }
} }
+8 -4
View File
@@ -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) {
+14 -1
View File
@@ -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();
+1
View File
@@ -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:
+13 -1
View File
@@ -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);
} }
} }
+25 -3
View File
@@ -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);
} }
+2 -2
View File
@@ -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;
+2 -2
View File
@@ -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
View File
@@ -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, &current)) {
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;
+7 -9
View File
@@ -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));
} }
}); });
} }
+244 -11
View File
@@ -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);
}
} }
} }
} }
+7 -1
View File
@@ -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();
} }
+11
View File
@@ -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);
} }
+9 -7
View File
@@ -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);
}; };
+18 -2
View File
@@ -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;
}
+2
View File
@@ -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
+26 -10
View File
@@ -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();
+1 -1
View File
@@ -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;
+12 -5
View File
@@ -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;
+86 -57
View File
@@ -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: {
+14 -5
View File
@@ -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;
+7 -11
View File
@@ -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() {
+34 -16
View File
@@ -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;
} }
} }
+2 -1
View File
@@ -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);
+1 -2
View File
@@ -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();
+2 -2
View File
@@ -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;
+40 -7
View File
@@ -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;
} }
} }
+1 -1
View File
@@ -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);
+20 -1
View File
@@ -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);
+1
View File
@@ -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);
} }
+2 -2
View File
@@ -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();