mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c590b87cff | |||
| 5cabd026ae | |||
| 7ccd938f9b | |||
| 88a210e82e |
@@ -411,6 +411,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
games/sdvx/sdvx_live2d.cpp
|
||||
games/sdvx/io.cpp
|
||||
games/sdvx/camera.cpp
|
||||
games/jb/bi2x_hook.cpp
|
||||
games/jb/jb.cpp
|
||||
games/jb/jb_touch.cpp
|
||||
games/jb/io.cpp
|
||||
|
||||
@@ -239,7 +239,7 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
|
||||
// SDVX Old cabinet mode
|
||||
if (avs::game::is_model("KFC") && avs::game::SPEC[0] != 'G' && avs::game::SPEC[0] != 'H')
|
||||
answer_type = 1;
|
||||
if (avs::game::is_model("L44"))
|
||||
if (avs::game::is_model({ "L44", "T44" }))
|
||||
answer_type = 2;
|
||||
|
||||
// check answer type
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#include "keypads.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "external/rapidjson/document.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
using namespace rapidjson;
|
||||
@@ -59,7 +60,6 @@ namespace api::modules {
|
||||
// get params
|
||||
auto keypad = req.params[0].GetUint();
|
||||
auto input = std::string(req.params[1].GetString());
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// process all chars
|
||||
for (auto c : input) {
|
||||
@@ -93,11 +93,11 @@ namespace api::modules {
|
||||
|
||||
// set
|
||||
eamuse_set_keypad_overrides(keypad, state);
|
||||
timer.sleep(sleep_time);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
|
||||
|
||||
// unset
|
||||
eamuse_set_keypad_overrides(keypad, 0);
|
||||
timer.sleep(sleep_time);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#include "controller.h"
|
||||
#include "serial.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
|
||||
@@ -17,7 +18,6 @@ namespace api {
|
||||
controller->init_state(this->state);
|
||||
this->thread = new std::thread([this] () {
|
||||
log_warning("api::serial", "listening on {} (baud: {})", this->port, this->baud);
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// read buffer
|
||||
uint8_t read_buffer[16*1024];
|
||||
@@ -162,7 +162,7 @@ namespace api {
|
||||
|
||||
// slow down on reconnect
|
||||
if (this->running) {
|
||||
timer.sleep(retry_time);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(retry_time));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1114,6 +1114,7 @@ namespace avs {
|
||||
#endif
|
||||
// jubeat
|
||||
{"jubeat.dll", 0x2000000},
|
||||
{"jubeat2019.dll", 0x10000000},
|
||||
|
||||
// MUSECA
|
||||
{"museca.dll", 0xC000000},
|
||||
|
||||
@@ -591,6 +591,7 @@ namespace avs {
|
||||
// for proper reporting of Omnimix and other song packs
|
||||
if (_stricmp(EA3_MODEL, "LDJ") == 0 ||
|
||||
_stricmp(EA3_MODEL, "L44") == 0 ||
|
||||
_stricmp(EA3_MODEL, "T44") == 0 ||
|
||||
_stricmp(EA3_MODEL, "M39") == 0 ||
|
||||
_stricmp(EA3_MODEL, "KFC") == 0)
|
||||
{
|
||||
|
||||
@@ -534,7 +534,7 @@ namespace games::iidx {
|
||||
if (mediaTypePointer && mediaTypePointer->IsString()) {
|
||||
std::string mediaType = mediaTypePointer->GetString();
|
||||
if (mediaType.length() > 0) {
|
||||
camera->m_selectedMediaTypeDescription = mediaType;
|
||||
camera->SetSelectedMediaTypeDescription(mediaType);
|
||||
camera->m_useAutoMediaType = false;
|
||||
} else {
|
||||
camera->m_useAutoMediaType = true;
|
||||
@@ -547,7 +547,7 @@ namespace games::iidx {
|
||||
std::string drawModeString = drawModePointer->GetString();
|
||||
for (int j = 0; j < DRAW_MODE_SIZE; j++) {
|
||||
if (DRAW_MODE_LABELS[j].compare(drawModeString) == 0) {
|
||||
camera->m_drawMode = (LocalCameraDrawMode) j;
|
||||
camera->m_drawMode.store((LocalCameraDrawMode) j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -556,12 +556,12 @@ namespace games::iidx {
|
||||
// Flip
|
||||
auto flipHorizontalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipHorizontal").Get(doc);
|
||||
if (flipHorizontalPointer && flipHorizontalPointer->IsBool()) {
|
||||
camera->m_flipHorizontal = flipHorizontalPointer->GetBool();
|
||||
camera->m_flipHorizontal.store(flipHorizontalPointer->GetBool());
|
||||
}
|
||||
|
||||
auto flipVerticalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipVertical").Get(doc);
|
||||
if (flipVerticalPointer && flipVerticalPointer->IsBool()) {
|
||||
camera->m_flipVertical = flipVerticalPointer->GetBool();
|
||||
camera->m_flipVertical.store(flipVerticalPointer->GetBool());
|
||||
}
|
||||
|
||||
// Allow manual control
|
||||
@@ -625,15 +625,17 @@ namespace games::iidx {
|
||||
if (camera->m_useAutoMediaType) {
|
||||
rapidjson::Pointer(root + "MediaType").Set(doc, "");
|
||||
} else {
|
||||
rapidjson::Pointer(root + "MediaType").Set(doc, camera->m_selectedMediaTypeDescription);
|
||||
const auto mediaTypeDescription = camera->GetSelectedMediaTypeDescription();
|
||||
rapidjson::Pointer(root + "MediaType").Set(doc, mediaTypeDescription);
|
||||
}
|
||||
|
||||
// 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
|
||||
rapidjson::Pointer(root + "FlipHorizontal").Set(doc, camera->m_flipHorizontal);
|
||||
rapidjson::Pointer(root + "FlipVertical").Set(doc, camera->m_flipVertical);
|
||||
rapidjson::Pointer(root + "FlipHorizontal").Set(doc, camera->m_flipHorizontal.load());
|
||||
rapidjson::Pointer(root + "FlipVertical").Set(doc, camera->m_flipVertical.load());
|
||||
|
||||
// Manual control
|
||||
rapidjson::Pointer(root + "AllowManualControl").Set(doc, camera->m_allowManualControl);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
||||
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include <atomic>
|
||||
#include <d3d9.h>
|
||||
#include <dxva2api.h>
|
||||
#include <mutex>
|
||||
@@ -66,6 +67,8 @@ extern std::string CAMERA_CONTROL_LABELS[];
|
||||
extern std::string DRAW_MODE_LABELS[];
|
||||
|
||||
namespace games::iidx {
|
||||
class IIDXCameraSourceReaderCallback;
|
||||
|
||||
namespace Camera {
|
||||
struct PlayVideoCamera {
|
||||
IDirect3DTexture9** d3d9_texture(const uintptr_t offset) {
|
||||
@@ -87,7 +90,7 @@ namespace games::iidx {
|
||||
|
||||
class IIDXLocalCamera {
|
||||
protected:
|
||||
virtual ~IIDXLocalCamera() {};
|
||||
virtual ~IIDXLocalCamera();
|
||||
|
||||
LONG m_nRefCount;
|
||||
CRITICAL_SECTION m_critsec;
|
||||
@@ -101,9 +104,14 @@ namespace games::iidx {
|
||||
// For reading frames from Camera
|
||||
IMFMediaSource *m_pSource = 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
|
||||
double m_frameRate = 0;
|
||||
LONG m_cameraWidth;
|
||||
LONG m_cameraHeight;
|
||||
|
||||
@@ -129,6 +137,12 @@ namespace games::iidx {
|
||||
LPDIRECT3DTEXTURE9 m_conversionTexture = 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)
|
||||
LPDIRECT3DTEXTURE9 m_transformTexture = nullptr;
|
||||
IDirect3DSurface9 *m_pTransformSurf = nullptr;
|
||||
@@ -153,21 +167,19 @@ namespace games::iidx {
|
||||
BOOL m_initialized = false;
|
||||
|
||||
// True if all the setup steps succeeded
|
||||
BOOL m_active = false;
|
||||
std::atomic_bool m_active = false;
|
||||
|
||||
// Media type select
|
||||
std::vector<MediaTypeInfo> m_mediaTypeInfos = {};
|
||||
int m_selectedMediaTypeIndex = 0;
|
||||
bool m_useAutoMediaType = true;
|
||||
IMFMediaType *m_pAutoMediaType = nullptr;
|
||||
std::string m_selectedMediaTypeDescription = "";
|
||||
bool m_allowManualControl = false;
|
||||
|
||||
LocalCameraDrawMode m_drawMode = DrawModeCrop4_3;
|
||||
std::atomic<LocalCameraDrawMode> m_drawMode = DrawModeCrop4_3;
|
||||
|
||||
// Render processing
|
||||
bool m_flipHorizontal = false;
|
||||
bool m_flipVertical = false;
|
||||
std::atomic_bool m_flipHorizontal = false;
|
||||
std::atomic_bool m_flipVertical = false;
|
||||
|
||||
IIDXLocalCamera(
|
||||
std::string name,
|
||||
@@ -184,11 +196,14 @@ namespace games::iidx {
|
||||
HRESULT GetCameraControlProp(int index, CameraControlProp *pProp);
|
||||
HRESULT SetCameraControlProp(int index, long value, long flags);
|
||||
HRESULT ResetCameraControlProps();
|
||||
HRESULT FlushDrawCommands();
|
||||
std::string GetName();
|
||||
std::string GetFriendlyName();
|
||||
std::string GetSymLink();
|
||||
int GetSelectedMediaTypeIndex();
|
||||
std::string GetSelectedMediaTypeDescription();
|
||||
void SetSelectedMediaTypeDescription(const std::string &description);
|
||||
HRESULT ChangeMediaType(IMFMediaType *pType);
|
||||
void RequestMediaType(IMFMediaType *pType);
|
||||
HRESULT StartCapture();
|
||||
void UpdateDrawRect();
|
||||
|
||||
@@ -197,12 +212,22 @@ namespace games::iidx {
|
||||
void CreateThread();
|
||||
MediaTypeInfo GetMediaTypeInfo(IMFMediaType *pType);
|
||||
std::string GetVideoFormatName(GUID subtype);
|
||||
HRESULT TryMediaType(IMFMediaType *pType, UINT32 *pBestWidth, double *pBestFrameRate);
|
||||
static bool CompareMediaTypes(const MediaTypeInfo &a, const MediaTypeInfo &b);
|
||||
bool MatchesPreferredAspect(const MediaTypeInfo &info) const;
|
||||
static bool IsBetterAutoType(const MediaTypeInfo &candidate, const MediaTypeInfo ¤t);
|
||||
IMFMediaType *FindBestNativeAutoType(bool requirePreferredAspect) const;
|
||||
IMFMediaType *FindBestAutoType(const GUID &subtype, bool requirePreferredAspect) const;
|
||||
HRESULT ValidateMediaType(IMFMediaType *pType);
|
||||
HRESULT InitTargetTexture();
|
||||
HRESULT EnsureTransformTextures();
|
||||
HRESULT InitCameraControl();
|
||||
void SetSelectedMediaType(int index, const std::string &description);
|
||||
bool HasPendingMediaType();
|
||||
HRESULT ApplyPendingMediaType();
|
||||
HRESULT UploadDecodedSample(IMFMediaBuffer *pSrcBuffer);
|
||||
HRESULT DrawSample(IMFMediaBuffer *pSrcBuffer);
|
||||
HRESULT ReadSample();
|
||||
LPDIRECT3DTEXTURE9 Render();
|
||||
HRESULT Render();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace games::iidx {
|
||||
_In_ UINT32 cInitialSize
|
||||
);
|
||||
|
||||
typedef HRESULT (__stdcall * MFCreateMediaType_t)(
|
||||
_Out_ IMFMediaType** ppMFType
|
||||
);
|
||||
|
||||
typedef HRESULT (__stdcall * MFEnumDeviceSources_t)(
|
||||
_In_ IMFAttributes* pAttributes,
|
||||
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
||||
@@ -35,6 +39,7 @@ namespace games::iidx {
|
||||
);
|
||||
|
||||
static MFCreateAttributes_t MFCreateAttributes = nullptr;
|
||||
static MFCreateMediaType_t MFCreateMediaType = nullptr;
|
||||
static MFEnumDeviceSources_t MFEnumDeviceSources = nullptr;
|
||||
static MFCreateSourceReaderFromMediaSource_t MFCreateSourceReaderFromMediaSource = nullptr;
|
||||
static MFGetService_t MFGetService = nullptr;
|
||||
@@ -66,6 +71,12 @@ namespace games::iidx {
|
||||
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)
|
||||
libutils::get_proc(mf_dll, "MFEnumDeviceSources");
|
||||
if (!MFEnumDeviceSources) {
|
||||
@@ -92,6 +103,11 @@ namespace games::iidx {
|
||||
return MFCreateAttributes(ppMFAttributes, cInitialSize);
|
||||
}
|
||||
|
||||
HRESULT WrappedMFCreateMediaType (
|
||||
_Out_ IMFMediaType** ppMFType) {
|
||||
return MFCreateMediaType(ppMFType);
|
||||
}
|
||||
|
||||
HRESULT WrappedMFEnumDeviceSources (
|
||||
_In_ IMFAttributes* pAttributes,
|
||||
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace games::iidx {
|
||||
_Out_ IMFAttributes** ppMFAttributes,
|
||||
_In_ UINT32 cInitialSize);
|
||||
|
||||
HRESULT WrappedMFCreateMediaType (
|
||||
_Out_ IMFMediaType** ppMFType);
|
||||
|
||||
HRESULT WrappedMFEnumDeviceSources (
|
||||
_In_ IMFAttributes* pAttributes,
|
||||
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "poke.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "windows.h"
|
||||
@@ -11,7 +12,6 @@
|
||||
#include "touch/native/inject.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
namespace games::iidx::poke {
|
||||
|
||||
@@ -125,8 +125,6 @@ namespace games::iidx::poke {
|
||||
// create new thread
|
||||
THREAD_RUNNING = true;
|
||||
THREAD = new std::thread([] {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// log
|
||||
log_info("poke", "enabled");
|
||||
|
||||
@@ -198,7 +196,7 @@ namespace games::iidx::poke {
|
||||
}
|
||||
|
||||
// slow down
|
||||
timer.sleep(50);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
|
||||
if (!touch_points.empty()) {
|
||||
|
||||
@@ -133,6 +133,7 @@ namespace games {
|
||||
buttons_help.insert({ jb, jb::get_buttons_help() });
|
||||
lights.insert({ jb, jb::get_lights() });
|
||||
file_hints[jb].push_back({"jubeat.dll"});
|
||||
file_hints[jb].push_back({"jubeat2019.dll"});
|
||||
|
||||
// mga
|
||||
const std::string mga("Metal Gear");
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "games/io.h"
|
||||
#include "games/jb/jb_touch.h"
|
||||
#include "io.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace games::jb {
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
std::array<uint8_t, 0x100> data;
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2;
|
||||
|
||||
struct AIO_NMGR_IOB2_VTABLE {
|
||||
std::array<void *, 10> unused;
|
||||
void (__fastcall *begin_manage)(AIO_NMGR_IOB2 *node_mgr);
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
AIO_NMGR_IOB2_VTABLE *vtable;
|
||||
std::array<uint8_t, 0x78> data;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_T44 {
|
||||
std::array<uint8_t, 0x80> data;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_T44_DEVSTATUS {
|
||||
std::array<uint8_t, 0x140> data;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
uint8_t data;
|
||||
};
|
||||
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0x80);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_T44_DEVSTATUS) == 0x140);
|
||||
|
||||
using aioIob2Bi2xT44_Create_t = AIO_IOB2_BI2X_T44 *(__fastcall *)(AIO_NMGR_IOB2 *node_mgr,
|
||||
uint32_t device_id);
|
||||
using aioIob2Bi2xT44_GetDeviceStatus_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||
AIO_IOB2_BI2X_T44_DEVSTATUS *status);
|
||||
using aioIob2Bi2xT44_IoReset_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node, uint32_t reset);
|
||||
using aioIob2Bi2xT44_SetWatchDogTimer_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node, uint8_t count);
|
||||
using aioIob2Bi2xT44_ControlCoinBlocker_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||
uint32_t slot, bool open);
|
||||
using aioIob2Bi2xT44_AddCounter_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||
uint32_t counter, uint32_t count);
|
||||
using aioIob2Bi2xT44_SetIccrLed_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node, uint32_t color);
|
||||
using aioIob2Bi2xT44_SetTapeLedData_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||
uint32_t tape, const void *data);
|
||||
using aioIob2Bi2x_OpenSciUsbCdc_t = AIO_SCI_COMM *(__fastcall *)(uint32_t serial_number);
|
||||
using aioIob2Bi2x_CreateWriteFirmContext_t = AIO_IOB2_BI2X_WRFIRM *(__fastcall *)(
|
||||
uint32_t serial_number, uint32_t iob_mask);
|
||||
using aioIob2Bi2x_DestroyWriteFirmContext_t = void (__fastcall *)(AIO_IOB2_BI2X_WRFIRM *context);
|
||||
using aioIob2Bi2x_WriteFirmGetState_t = int32_t (__fastcall *)(AIO_IOB2_BI2X_WRFIRM *context);
|
||||
using aioIob2Bi2x_WriteFirmIsCompleted_t = bool (__fastcall *)(int32_t state);
|
||||
using aioIob2Bi2x_WriteFirmIsError_t = bool (__fastcall *)(int32_t state);
|
||||
using aioNMgrIob2_Create_t = AIO_NMGR_IOB2 *(__fastcall *)(AIO_SCI_COMM *sci, uint32_t mode);
|
||||
using aioSci_Destroy_t = void (__fastcall *)(AIO_SCI_COMM *sci);
|
||||
using aioNodeMgr_Destroy_t = void (__fastcall *)(AIO_NMGR_IOB2 *node_mgr);
|
||||
using aioNodeCtl_Destroy_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node);
|
||||
using aioNodeCtl_UpdateDevicesStatus_t = void (__fastcall *)();
|
||||
|
||||
static aioIob2Bi2xT44_Create_t aioIob2Bi2xT44_Create_orig = nullptr;
|
||||
static aioIob2Bi2xT44_GetDeviceStatus_t aioIob2Bi2xT44_GetDeviceStatus_orig = nullptr;
|
||||
static aioIob2Bi2xT44_IoReset_t aioIob2Bi2xT44_IoReset_orig = nullptr;
|
||||
static aioIob2Bi2xT44_SetWatchDogTimer_t aioIob2Bi2xT44_SetWatchDogTimer_orig = nullptr;
|
||||
static aioIob2Bi2xT44_ControlCoinBlocker_t aioIob2Bi2xT44_ControlCoinBlocker_orig = nullptr;
|
||||
static aioIob2Bi2xT44_AddCounter_t aioIob2Bi2xT44_AddCounter_orig = nullptr;
|
||||
static aioIob2Bi2xT44_SetIccrLed_t aioIob2Bi2xT44_SetIccrLed_orig = nullptr;
|
||||
static aioIob2Bi2xT44_SetTapeLedData_t aioIob2Bi2xT44_SetTapeLedData_orig = nullptr;
|
||||
static aioIob2Bi2x_OpenSciUsbCdc_t aioIob2Bi2x_OpenSciUsbCdc_orig = nullptr;
|
||||
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
|
||||
static aioIob2Bi2x_DestroyWriteFirmContext_t aioIob2Bi2x_DestroyWriteFirmContext_orig = nullptr;
|
||||
static aioIob2Bi2x_WriteFirmGetState_t aioIob2Bi2x_WriteFirmGetState_orig = nullptr;
|
||||
static aioIob2Bi2x_WriteFirmIsCompleted_t aioIob2Bi2x_WriteFirmIsCompleted_orig = nullptr;
|
||||
static aioIob2Bi2x_WriteFirmIsError_t aioIob2Bi2x_WriteFirmIsError_orig = nullptr;
|
||||
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
|
||||
static aioSci_Destroy_t aioSci_Destroy_orig = nullptr;
|
||||
static aioNodeMgr_Destroy_t aioNodeMgr_Destroy_orig = nullptr;
|
||||
static aioNodeCtl_Destroy_t aioNodeCtl_Destroy_orig = nullptr;
|
||||
static aioNodeCtl_UpdateDevicesStatus_t aioNodeCtl_UpdateDevicesStatus_orig = nullptr;
|
||||
|
||||
static AIO_SCI_COMM *aio_sci_comm = nullptr;
|
||||
static AIO_NMGR_IOB2 *aio_node_mgr = nullptr;
|
||||
static AIO_IOB2_BI2X_T44 *aio_t44 = nullptr;
|
||||
static AIO_IOB2_BI2X_WRFIRM *aio_write_firm = nullptr;
|
||||
static uint8_t input_counter = 0;
|
||||
|
||||
static void __fastcall aioNMgrIob_BeginManage(AIO_NMGR_IOB2 *) {
|
||||
}
|
||||
|
||||
static AIO_NMGR_IOB2_VTABLE aio_node_mgr_vtable {
|
||||
{},
|
||||
aioNMgrIob_BeginManage,
|
||||
};
|
||||
|
||||
static AIO_SCI_COMM *__fastcall aioIob2Bi2x_OpenSciUsbCdc(uint32_t) {
|
||||
aio_sci_comm = new AIO_SCI_COMM {};
|
||||
return aio_sci_comm;
|
||||
}
|
||||
|
||||
static AIO_NMGR_IOB2 *__fastcall aioNMgrIob2_Create(AIO_SCI_COMM *sci, uint32_t mode) {
|
||||
if (sci != aio_sci_comm) {
|
||||
return aioNMgrIob2_Create_orig(sci, mode);
|
||||
}
|
||||
|
||||
aio_node_mgr = new AIO_NMGR_IOB2 {};
|
||||
aio_node_mgr->vtable = &aio_node_mgr_vtable;
|
||||
return aio_node_mgr;
|
||||
}
|
||||
|
||||
static AIO_IOB2_BI2X_T44 *__fastcall aioIob2Bi2xT44_Create(
|
||||
AIO_NMGR_IOB2 *node_mgr, uint32_t device_id) {
|
||||
if (node_mgr != aio_node_mgr) {
|
||||
return aioIob2Bi2xT44_Create_orig(node_mgr, device_id);
|
||||
}
|
||||
|
||||
log_info("jb::bi2x", "T44 node created");
|
||||
aio_t44 = new AIO_IOB2_BI2X_T44 {};
|
||||
return aio_t44;
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xT44_GetDeviceStatus(
|
||||
AIO_IOB2_BI2X_T44 *node, AIO_IOB2_BI2X_T44_DEVSTATUS *status) {
|
||||
if (node != aio_t44) {
|
||||
return aioIob2Bi2xT44_GetDeviceStatus_orig(node, status);
|
||||
}
|
||||
|
||||
RI_MGR->devices_flush_output();
|
||||
std::memset(status, 0, sizeof(*status));
|
||||
|
||||
status->data[0x00] = input_counter;
|
||||
status->data[0x03] = input_counter++;
|
||||
status->data[0x0A] = static_cast<uint8_t>(eamuse_coin_get_stock());
|
||||
|
||||
games::jb::touch_update();
|
||||
const auto touched = games::jb::touch_state();
|
||||
auto &buttons = get_buttons();
|
||||
|
||||
status->data[0x04] = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]) ? 0xFF : 0;
|
||||
status->data[0x05] = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]) ? 0xFF : 0;
|
||||
status->data[0x06] = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]) ? 0xFF : 0;
|
||||
|
||||
static constexpr size_t PANEL_ORDER[16] = {
|
||||
3, 7, 11, 15,
|
||||
2, 6, 10, 14,
|
||||
1, 5, 9, 13,
|
||||
0, 4, 8, 12,
|
||||
};
|
||||
for (size_t status_index = 0; status_index < std::size(PANEL_ORDER); status_index++) {
|
||||
const auto panel_index = PANEL_ORDER[status_index];
|
||||
if (touched[panel_index] || GameAPI::Buttons::getState(
|
||||
RI_MGR, buttons[Buttons::Button1 + panel_index])) {
|
||||
status->data[0x0F + status_index] = 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xT44_IoReset(AIO_IOB2_BI2X_T44 *node, uint32_t reset) {
|
||||
if (node != aio_t44) {
|
||||
return aioIob2Bi2xT44_IoReset_orig(node, reset);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xT44_SetWatchDogTimer(AIO_IOB2_BI2X_T44 *node, uint8_t count) {
|
||||
if (node != aio_t44) {
|
||||
return aioIob2Bi2xT44_SetWatchDogTimer_orig(node, count);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xT44_ControlCoinBlocker(
|
||||
AIO_IOB2_BI2X_T44 *node, uint32_t slot, bool open) {
|
||||
if (node != aio_t44) {
|
||||
return aioIob2Bi2xT44_ControlCoinBlocker_orig(node, slot, open);
|
||||
}
|
||||
|
||||
eamuse_coin_set_block(!open);
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xT44_AddCounter(
|
||||
AIO_IOB2_BI2X_T44 *node, uint32_t counter, uint32_t count) {
|
||||
if (node != aio_t44) {
|
||||
return aioIob2Bi2xT44_AddCounter_orig(node, counter, count);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xT44_SetIccrLed(AIO_IOB2_BI2X_T44 *node, uint32_t color) {
|
||||
if (node != aio_t44) {
|
||||
return aioIob2Bi2xT44_SetIccrLed_orig(node, color);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xT44_SetTapeLedData(
|
||||
AIO_IOB2_BI2X_T44 *node, uint32_t tape, const void *data) {
|
||||
if (node != aio_t44) {
|
||||
return aioIob2Bi2xT44_SetTapeLedData_orig(node, tape, data);
|
||||
}
|
||||
}
|
||||
|
||||
static AIO_IOB2_BI2X_WRFIRM *__fastcall aioIob2Bi2x_CreateWriteFirmContext(uint32_t, uint32_t) {
|
||||
aio_write_firm = new AIO_IOB2_BI2X_WRFIRM {};
|
||||
return aio_write_firm;
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2x_DestroyWriteFirmContext(AIO_IOB2_BI2X_WRFIRM *context) {
|
||||
if (context != aio_write_firm) {
|
||||
return aioIob2Bi2x_DestroyWriteFirmContext_orig(context);
|
||||
}
|
||||
|
||||
delete aio_write_firm;
|
||||
aio_write_firm = nullptr;
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioIob2Bi2x_WriteFirmGetState(AIO_IOB2_BI2X_WRFIRM *context) {
|
||||
if (context != aio_write_firm) {
|
||||
return aioIob2Bi2x_WriteFirmGetState_orig(context);
|
||||
}
|
||||
return 8;
|
||||
}
|
||||
|
||||
static bool __fastcall aioIob2Bi2x_WriteFirmIsCompleted(int32_t state) {
|
||||
if (aio_write_firm) {
|
||||
return true;
|
||||
}
|
||||
return aioIob2Bi2x_WriteFirmIsCompleted_orig(state);
|
||||
}
|
||||
|
||||
static bool __fastcall aioIob2Bi2x_WriteFirmIsError(int32_t state) {
|
||||
if (aio_write_firm) {
|
||||
return false;
|
||||
}
|
||||
return aioIob2Bi2x_WriteFirmIsError_orig(state);
|
||||
}
|
||||
|
||||
static void __fastcall aioSci_Destroy(AIO_SCI_COMM *sci) {
|
||||
if (sci != aio_sci_comm) {
|
||||
return aioSci_Destroy_orig(sci);
|
||||
}
|
||||
|
||||
delete aio_sci_comm;
|
||||
aio_sci_comm = nullptr;
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB2 *node_mgr) {
|
||||
if (node_mgr != aio_node_mgr) {
|
||||
return aioNodeMgr_Destroy_orig(node_mgr);
|
||||
}
|
||||
|
||||
delete aio_node_mgr;
|
||||
aio_node_mgr = nullptr;
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeCtl_Destroy(AIO_IOB2_BI2X_T44 *node) {
|
||||
if (node != aio_t44) {
|
||||
return aioNodeCtl_Destroy_orig(node);
|
||||
}
|
||||
|
||||
delete aio_t44;
|
||||
aio_t44 = nullptr;
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeCtl_UpdateDevicesStatus() {
|
||||
}
|
||||
|
||||
void bi2x_hook_init() {
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
|
||||
log_info("jb::bi2x", "initializing T44 hooks");
|
||||
|
||||
const auto libaio_iob2_video = "libaio-iob2_video.dll";
|
||||
detour::trampoline_try(libaio_iob2_video, "aioIob2Bi2xT44_Create",
|
||||
aioIob2Bi2xT44_Create, &aioIob2Bi2xT44_Create_orig);
|
||||
detour::trampoline_try(libaio_iob2_video,
|
||||
"?GetDeviceStatus@AIO_IOB2_BI2X_T44@@QEBAXAEAUDEVSTATUS@1@@Z",
|
||||
aioIob2Bi2xT44_GetDeviceStatus, &aioIob2Bi2xT44_GetDeviceStatus_orig);
|
||||
detour::trampoline_try(libaio_iob2_video, "?IoReset@AIO_IOB2_BI2X_T44@@QEAAXI@Z",
|
||||
aioIob2Bi2xT44_IoReset, &aioIob2Bi2xT44_IoReset_orig);
|
||||
detour::trampoline_try(libaio_iob2_video,
|
||||
"?SetWatchDogTimer@AIO_IOB2_BI2X_T44@@QEAAXE@Z",
|
||||
aioIob2Bi2xT44_SetWatchDogTimer, &aioIob2Bi2xT44_SetWatchDogTimer_orig);
|
||||
detour::trampoline_try(libaio_iob2_video,
|
||||
"?ControlCoinBlocker@AIO_IOB2_BI2X_T44@@QEAAXI_N@Z",
|
||||
aioIob2Bi2xT44_ControlCoinBlocker, &aioIob2Bi2xT44_ControlCoinBlocker_orig);
|
||||
detour::trampoline_try(libaio_iob2_video, "?AddCounter@AIO_IOB2_BI2X_T44@@QEAAXII@Z",
|
||||
aioIob2Bi2xT44_AddCounter, &aioIob2Bi2xT44_AddCounter_orig);
|
||||
detour::trampoline_try(libaio_iob2_video, "?SetIccrLed@AIO_IOB2_BI2X_T44@@QEAAXI@Z",
|
||||
aioIob2Bi2xT44_SetIccrLed, &aioIob2Bi2xT44_SetIccrLed_orig);
|
||||
detour::trampoline_try(libaio_iob2_video,
|
||||
"?SetTapeLedData@AIO_IOB2_BI2X_T44@@QEAAXIPEBX@Z",
|
||||
aioIob2Bi2xT44_SetTapeLedData, &aioIob2Bi2xT44_SetTapeLedData_orig);
|
||||
|
||||
const auto libaio_iob = "libaio-iob.dll";
|
||||
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_OpenSciUsbCdc",
|
||||
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
||||
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_CreateWriteFirmContext",
|
||||
aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig);
|
||||
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_DestroyWriteFirmContext",
|
||||
aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig);
|
||||
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_WriteFirmGetState",
|
||||
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
|
||||
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_WriteFirmIsCompleted",
|
||||
aioIob2Bi2x_WriteFirmIsCompleted, &aioIob2Bi2x_WriteFirmIsCompleted_orig);
|
||||
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_WriteFirmIsError",
|
||||
aioIob2Bi2x_WriteFirmIsError, &aioIob2Bi2x_WriteFirmIsError_orig);
|
||||
detour::trampoline_try(libaio_iob, "aioNMgrIob2_Create",
|
||||
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||
|
||||
const auto libaio = "libaio.dll";
|
||||
detour::trampoline_try(libaio, "aioSci_Destroy", aioSci_Destroy, &aioSci_Destroy_orig);
|
||||
detour::trampoline_try(libaio, "aioNodeMgr_Destroy",
|
||||
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
|
||||
detour::trampoline_try(libaio, "aioNodeCtl_Destroy",
|
||||
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
|
||||
detour::trampoline_try(libaio, "aioNodeCtl_UpdateDevicesStatus",
|
||||
aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
namespace games::jb {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <windows.h>
|
||||
#include <filesystem>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "bi2x_hook.h"
|
||||
#include "cfg/configurator.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/detour.h"
|
||||
@@ -10,6 +12,8 @@
|
||||
|
||||
namespace games::jb {
|
||||
|
||||
#if !SPICE64
|
||||
|
||||
// fixes "IP ADDR CHANGE" errors with unusual network setups (e.g. a VPN)
|
||||
static BOOL __stdcall network_addr_is_changed() {
|
||||
return 0;
|
||||
@@ -31,6 +35,8 @@ namespace games::jb {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
JBGame::JBGame() : Game("Jubeat") {
|
||||
}
|
||||
|
||||
@@ -60,8 +66,18 @@ namespace games::jb {
|
||||
void JBGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
#if SPICE64
|
||||
if (avs::game::DLL_NAME == "jubeat2019.dll") {
|
||||
libutils::load_library("libaio.dll");
|
||||
libutils::load_library("libaio-iob.dll");
|
||||
libutils::load_library("libaio-iob2_video.dll");
|
||||
bi2x_hook_init();
|
||||
}
|
||||
#endif
|
||||
|
||||
touch_attach();
|
||||
|
||||
#if !SPICE64
|
||||
// enable debug logging of gftools
|
||||
HMODULE gftools = libutils::try_module("gftools.dll");
|
||||
detour::inline_hook((void *) GFDbgSetReportFunc, libutils::try_proc(
|
||||
@@ -75,6 +91,8 @@ namespace games::jb {
|
||||
network, "network_get_network_check_info"));
|
||||
detour::inline_hook((void *) network_get_dhcp_result, libutils::try_proc(
|
||||
network, "network_get_dhcp_result"));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void JBGame::detach() {
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
// (6, 8) in landscape.
|
||||
#define JB_BUTTON_SIZE 160
|
||||
#define JB_MAX_BUTTON_GAP 38
|
||||
#define JB_T44_BUTTON_SIZE 224
|
||||
#define JB_T44_BUTTON_GAP 33
|
||||
|
||||
// improved and plus modes use this reach around each button. must be >= the
|
||||
// diagonal half of the widest gap (~27px) so the grid centre still reaches a button.
|
||||
@@ -41,6 +43,7 @@ namespace games::jb {
|
||||
static std::atomic_bool TOUCH_ENABLE = false;
|
||||
static bool TOUCH_ATTACHED = false;
|
||||
static bool IS_PORTRAIT = true;
|
||||
static bool IS_T44 = false;
|
||||
static std::atomic_uint16_t TOUCH_STATE = 0;
|
||||
|
||||
// fixed-size contact view used by the debug overlay
|
||||
@@ -85,35 +88,45 @@ namespace games::jb {
|
||||
// --- touch geometry ------------------------------------------------------
|
||||
// gaps between the four buttons along one axis (the middle gap is 1px wider)
|
||||
static const int JB_BUTTON_GAPS[3] = { 37, JB_MAX_BUTTON_GAP, 37 };
|
||||
static const int JB_T44_BUTTON_GAPS[3] = {
|
||||
JB_T44_BUTTON_GAP,
|
||||
JB_T44_BUTTON_GAP,
|
||||
JB_T44_BUTTON_GAP,
|
||||
};
|
||||
|
||||
struct AxisGeometry {
|
||||
int size;
|
||||
int button[4]; // left/top edge of each button
|
||||
};
|
||||
|
||||
// left/top edges of the four buttons along one axis, starting at `first`
|
||||
static AxisGeometry axis_geometry(int first) {
|
||||
static AxisGeometry axis_geometry(int first, int size, const int gaps[3]) {
|
||||
AxisGeometry g {};
|
||||
g.size = size;
|
||||
g.button[0] = first;
|
||||
for (int i = 1; i < 4; i++) {
|
||||
g.button[i] = g.button[i - 1] + JB_BUTTON_SIZE + JB_BUTTON_GAPS[i - 1];
|
||||
g.button[i] = g.button[i - 1] + size + gaps[i - 1];
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
// button edges for the current orientation
|
||||
static void touch_geometry(AxisGeometry &gx, AxisGeometry &gy) {
|
||||
if (IS_PORTRAIT) {
|
||||
gx = axis_geometry(8);
|
||||
gy = axis_geometry(602);
|
||||
if (IS_T44) {
|
||||
gx = axis_geometry(37, JB_T44_BUTTON_SIZE, JB_T44_BUTTON_GAPS);
|
||||
gy = axis_geometry(864, JB_T44_BUTTON_SIZE, JB_T44_BUTTON_GAPS);
|
||||
} else if (IS_PORTRAIT) {
|
||||
gx = axis_geometry(8, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||
gy = axis_geometry(602, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||
} else {
|
||||
gx = axis_geometry(6);
|
||||
gy = axis_geometry(8);
|
||||
gx = axis_geometry(6, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||
gy = axis_geometry(8, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||
}
|
||||
}
|
||||
|
||||
// distance from `p` to a button along one axis (0 when inside)
|
||||
static int axis_distance(int p, int button) {
|
||||
int end = button + JB_BUTTON_SIZE - 1;
|
||||
static int axis_distance(int p, int button, int size) {
|
||||
int end = button + size - 1;
|
||||
if (p < button) {
|
||||
return button - p;
|
||||
}
|
||||
@@ -131,8 +144,8 @@ namespace games::jb {
|
||||
int best_dist = 0;
|
||||
for (int r = 0; r < 4; r++) {
|
||||
for (int c = 0; c < 4; c++) {
|
||||
int dx = axis_distance(px, gx.button[c]);
|
||||
int dy = axis_distance(py, gy.button[r]);
|
||||
int dx = axis_distance(px, gx.button[c], gx.size);
|
||||
int dy = axis_distance(py, gy.button[r], gy.size);
|
||||
int dist = dx * dx + dy * dy;
|
||||
if (dist <= radius * radius && (best_index < 0 || dist < best_dist)) {
|
||||
best_dist = dist;
|
||||
@@ -145,7 +158,8 @@ namespace games::jb {
|
||||
|
||||
// detection reach for the current algorithm (0 = register only inside a button)
|
||||
static int touch_radius() {
|
||||
return TOUCH_ALGORITHM == AcAccurate ? 0 : JB_TOUCH_RADIUS;
|
||||
return TOUCH_ALGORITHM == AcAccurate || (IS_T44 && TOUCH_ALGORITHM == Legacy) ?
|
||||
0 : JB_TOUCH_RADIUS;
|
||||
}
|
||||
|
||||
// mark the buttons a touch at (px, py) hits: only the nearest within `radius`, or
|
||||
@@ -162,8 +176,8 @@ namespace games::jb {
|
||||
}
|
||||
for (int r = 0; r < 4; r++) {
|
||||
for (int c = 0; c < 4; c++) {
|
||||
int dx = axis_distance(px, gx.button[c]);
|
||||
int dy = axis_distance(py, gy.button[r]);
|
||||
int dx = axis_distance(px, gx.button[c], gx.size);
|
||||
int dy = axis_distance(py, gy.button[r], gy.size);
|
||||
if (dx * dx + dy * dy <= radius * radius) {
|
||||
state |= uint16_t(1) << (r * 4 + c);
|
||||
}
|
||||
@@ -184,11 +198,15 @@ namespace games::jb {
|
||||
// one-time touch window attach
|
||||
if (!TOUCH_ATTACHED) {
|
||||
|
||||
IS_T44 = avs::game::is_model("T44");
|
||||
IS_PORTRAIT = IS_T44 || avs::game::is_model("L44");
|
||||
|
||||
// find the game window: prefer the foreground window, else search by
|
||||
// title (the model name prefixes the window title in every version)
|
||||
// title (T44 uses a fixed title instead of the model prefix)
|
||||
const char *window_title = IS_T44 ? "jubeat 10 main" : avs::game::MODEL;
|
||||
HWND wnd = GetForegroundWindow();
|
||||
if (!string_begins_with(GetActiveWindowTitle(), avs::game::MODEL)) {
|
||||
wnd = FindWindowBeginsWith(avs::game::MODEL);
|
||||
if (!string_begins_with(GetActiveWindowTitle(), window_title)) {
|
||||
wnd = FindWindowBeginsWith(window_title);
|
||||
}
|
||||
if (!wnd) {
|
||||
log_warning("jubeat", "could not find window handle for touch");
|
||||
@@ -197,10 +215,6 @@ namespace games::jb {
|
||||
return;
|
||||
}
|
||||
|
||||
// only the L44 model runs in portrait; set this before starting the
|
||||
// touch-window thread so the renderer only observes the final value
|
||||
IS_PORTRAIT = avs::game::is_model("L44");
|
||||
|
||||
log_info("jubeat", "using window handle for touch: {}", fmt::ptr(wnd));
|
||||
|
||||
// let the rawinput stack correct the aspect ratio
|
||||
@@ -232,7 +246,7 @@ namespace games::jb {
|
||||
return !touch_matured(tp, now_ms, threshold_ms);
|
||||
});
|
||||
|
||||
if (TOUCH_ALGORITHM == Legacy) {
|
||||
if (TOUCH_ALGORITHM == Legacy && !IS_T44) {
|
||||
|
||||
// legacy: evenly divide the play area into a 4x4 grid
|
||||
auto offset = IS_PORTRAIT ? 580 : 0;
|
||||
@@ -311,7 +325,7 @@ namespace games::jb {
|
||||
for (int c = 0; c < 4; c++) {
|
||||
cells[r * 4 + c] = {
|
||||
gx.button[c], gy.button[r],
|
||||
gx.button[c] + JB_BUTTON_SIZE, gy.button[r] + JB_BUTTON_SIZE
|
||||
gx.button[c] + gx.size, gy.button[r] + gy.size
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -352,8 +366,8 @@ namespace games::jb {
|
||||
const AxisGeometry &gx, const AxisGeometry &gy, int arc_radius) {
|
||||
int c = index % 4;
|
||||
int r = index / 4;
|
||||
double mid = std::atan2((gy.button[r] + JB_BUTTON_SIZE / 2) - py,
|
||||
(gx.button[c] + JB_BUTTON_SIZE / 2) - px);
|
||||
double mid = std::atan2((gy.button[r] + gy.size / 2) - py,
|
||||
(gx.button[c] + gx.size / 2) - px);
|
||||
const double quarter = 3.14159265358979323846 / 2.0;
|
||||
const int segments = 16;
|
||||
POINT arc[segments + 1];
|
||||
@@ -425,7 +439,7 @@ namespace games::jb {
|
||||
}
|
||||
|
||||
// legacy divides the field evenly; the other algorithms use button squares
|
||||
bool legacy = (TOUCH_ALGORITHM == Legacy);
|
||||
bool legacy = TOUCH_ALGORITHM == Legacy && !IS_T44;
|
||||
AxisGeometry gx {}, gy {};
|
||||
if (!legacy) {
|
||||
touch_geometry(gx, gy);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "poke.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
@@ -11,7 +12,6 @@
|
||||
#include "touch/native/inject.h"
|
||||
#include "touch/native/nativetouchhook.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
namespace games::nost::poke {
|
||||
|
||||
@@ -68,8 +68,6 @@ namespace games::nost::poke {
|
||||
// create new thread
|
||||
THREAD_RUNNING = true;
|
||||
THREAD = new std::thread([] {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
const int swipe_anim_total_frames = 6;
|
||||
const int swipe_anim_y = 300;
|
||||
|
||||
@@ -194,7 +192,7 @@ namespace games::nost::poke {
|
||||
}
|
||||
|
||||
// slow down
|
||||
timer.sleep(30);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
}
|
||||
|
||||
if (contact_active) {
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "hooks/audio/util.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
NullDiscardBackend::~NullDiscardBackend() {
|
||||
this->running = false;
|
||||
@@ -125,8 +125,6 @@ HRESULT NullDiscardBackend::on_release_buffer(uint32_t, DWORD) {
|
||||
void NullDiscardBackend::pace_loop() {
|
||||
using namespace std::chrono;
|
||||
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// 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.
|
||||
const auto period = duration_cast<steady_clock::duration>(
|
||||
@@ -136,6 +134,6 @@ void NullDiscardBackend::pace_loop() {
|
||||
if (this->relay_handle) {
|
||||
SetEvent(this->relay_handle);
|
||||
}
|
||||
timer.sleep(period);
|
||||
std::this_thread::sleep_for(period);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,8 +296,9 @@ void hooks::lang::early_init() {
|
||||
&GetLocaleInfoA_orig);
|
||||
}
|
||||
|
||||
// for TDJ subscreen search keyboard
|
||||
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
|
||||
// for TDJ subscreen search keyboard and T44 narrow-string handling
|
||||
if ((avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) ||
|
||||
avs::game::is_model("T44")) {
|
||||
log_info("hooks::lang", "hooking IsDBCSLeadByte");
|
||||
detour::trampoline_try(
|
||||
"kernel32.dll",
|
||||
@@ -306,7 +307,7 @@ void hooks::lang::early_init() {
|
||||
&IsDBCSLeadByte_orig);
|
||||
}
|
||||
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
if (games::gitadora::is_arena_model() || avs::game::is_model("T44")) {
|
||||
log_info("hooks::lang", "hooking WideCharToMultiByte");
|
||||
detour::trampoline_try(
|
||||
"kernel32.dll",
|
||||
|
||||
@@ -1855,6 +1855,13 @@ int main_implementation(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (check_dll("jubeat2019.dll")) {
|
||||
avs::game::DLL_NAME = "jubeat2019.dll";
|
||||
attach_io = true;
|
||||
attach_jb = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// RB
|
||||
if (check_dll("reflecbeat.dll")) {
|
||||
avs::game::DLL_NAME = "reflecbeat.dll";
|
||||
@@ -2283,7 +2290,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
games.push_back(new games::sdvx::SDVXGame());
|
||||
}
|
||||
if (attach_jb) {
|
||||
avs::core::set_default_heap_size("jubeat.dll");
|
||||
avs::core::set_default_heap_size(avs::game::DLL_NAME);
|
||||
games.push_back(new games::jb::JBGame());
|
||||
}
|
||||
if (attach_nostalgia) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "eamuse.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
|
||||
@@ -10,7 +11,6 @@
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
#include "overlay/overlay.h"
|
||||
@@ -335,7 +335,6 @@ void eamuse_coin_start_thread() {
|
||||
COIN_INPUT_THREAD = new std::thread([]() {
|
||||
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
|
||||
static bool COIN_INPUT_KEY_STATE = false;
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
while (COIN_INPUT_THREAD_ACTIVE) {
|
||||
|
||||
// check input key
|
||||
@@ -355,7 +354,7 @@ void eamuse_coin_start_thread() {
|
||||
}
|
||||
|
||||
// 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()};
|
||||
|
||||
std::optional<uint8_t> active_unit = std::nullopt;
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
while (PIN_MACRO_THREAD_ACTIVE) {
|
||||
// wait for key press or auto-trigger
|
||||
@@ -438,7 +436,7 @@ void eamuse_pin_macro_start_thread() {
|
||||
}
|
||||
|
||||
if (!active_unit.has_value()) {
|
||||
timer.sleep(20);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -454,22 +452,22 @@ void eamuse_pin_macro_start_thread() {
|
||||
eamuse_set_keypad_overrides(unit, keypad_overrides[char_index]);
|
||||
}
|
||||
pin_index[unit]++;
|
||||
timer.sleep(100);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// clear
|
||||
eamuse_set_keypad_overrides(unit, 0);
|
||||
timer.sleep(50);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
// end of PIN
|
||||
if (pin_index[unit] == PIN_MACRO_VALUES[unit].length()) {
|
||||
active_unit = std::nullopt;
|
||||
timer.sleep(120);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(120));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
timer.sleep(200);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -675,7 +673,8 @@ void eamuse_autodetect_game() {
|
||||
eamuse_set_game("Beatmania IIDX");
|
||||
else if (avs::game::is_model("J44") ||
|
||||
avs::game::is_model("K44") ||
|
||||
avs::game::is_model("L44"))
|
||||
avs::game::is_model("L44") ||
|
||||
avs::game::is_model("T44"))
|
||||
eamuse_set_game("Jubeat");
|
||||
else if (avs::game::is_model("KDM"))
|
||||
eamuse_set_game("Dance Evolution");
|
||||
|
||||
@@ -495,6 +495,14 @@ namespace wintouchemu {
|
||||
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;
|
||||
|
||||
@@ -908,6 +908,17 @@ bool overlay::SpiceOverlay::get_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() {
|
||||
return this->get_active() && ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow);
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ namespace overlay {
|
||||
void show_main_menu();
|
||||
void set_active(bool active);
|
||||
bool get_active();
|
||||
bool accepts_subscreen_mouse_input();
|
||||
bool has_focus();
|
||||
bool hotkeys_triggered();
|
||||
|
||||
@@ -143,20 +144,20 @@ namespace overlay {
|
||||
return this->hWnd;
|
||||
}
|
||||
|
||||
bool can_transform_touch_input() {
|
||||
return (this->subscreen_mouse_handler != nullptr);
|
||||
bool has_subscreen_touch_transform() {
|
||||
return (this->subscreen_touch_transform != nullptr);
|
||||
}
|
||||
|
||||
bool transform_touch_point(LONG *x, LONG *y) {
|
||||
if (this->get_active() && this->subscreen_mouse_handler) {
|
||||
return this->subscreen_mouse_handler(x, y);
|
||||
if (this->get_active() && this->subscreen_touch_transform) {
|
||||
return this->subscreen_touch_transform(x, y);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_subscreen_mouse_handler(const std::function<bool(LONG *, LONG *)> &f) {
|
||||
this->subscreen_mouse_handler = f;
|
||||
void set_subscreen_touch_transform(const std::function<bool(LONG *, LONG *)> &f) {
|
||||
this->subscreen_touch_transform = f;
|
||||
}
|
||||
|
||||
// renderer
|
||||
@@ -203,7 +204,7 @@ namespace overlay {
|
||||
|
||||
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 toggle_down = false;
|
||||
@@ -216,6 +217,7 @@ namespace overlay {
|
||||
// so render() never runs without a matching NewFrame.
|
||||
bool has_pending_frame = false;
|
||||
|
||||
bool is_subscreen_overlay_visible();
|
||||
void init();
|
||||
void add_font(const char* font, ImFontConfig* config, const ImWchar* glyphs);
|
||||
};
|
||||
|
||||
@@ -69,6 +69,7 @@ void overlay::Window::build() {
|
||||
|
||||
// check if active
|
||||
if (!this->active) {
|
||||
this->mouse_hovered = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -106,10 +107,18 @@ void overlay::Window::build() {
|
||||
// top spot. suppress that so only an explicit focus request (see below)
|
||||
// decides what comes to the front.
|
||||
const std::string window_id = this->title + "###" + to_string(this);
|
||||
if (ImGui::Begin(
|
||||
const bool build_contents = ImGui::Begin(
|
||||
window_id.c_str(),
|
||||
&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
|
||||
// (ImGuiCond_Once), so compute them a single time instead of every frame
|
||||
@@ -154,6 +163,9 @@ void overlay::Window::build() {
|
||||
|
||||
} else {
|
||||
|
||||
// raw content does not have an ImGui window to hover
|
||||
this->mouse_hovered = false;
|
||||
|
||||
// add raw content
|
||||
this->build_content();
|
||||
}
|
||||
@@ -201,3 +213,7 @@ bool overlay::Window::get_active() {
|
||||
// now it depends on us
|
||||
return this->active;
|
||||
}
|
||||
|
||||
bool overlay::Window::is_mouse_hovered() {
|
||||
return this->mouse_hovered;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace overlay {
|
||||
void toggle_active();
|
||||
void set_active(bool active);
|
||||
bool get_active();
|
||||
bool is_mouse_hovered();
|
||||
|
||||
// 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
|
||||
@@ -37,6 +38,7 @@ namespace overlay {
|
||||
// set when the window transitions to visible so build() raises it to the
|
||||
// top of the z-order on the next frame
|
||||
bool request_focus = false;
|
||||
bool mouse_hovered = false;
|
||||
std::vector<Window*> children;
|
||||
|
||||
// settings
|
||||
|
||||
@@ -82,9 +82,14 @@ namespace overlay::windows {
|
||||
ImGui::Text("Rendering");
|
||||
|
||||
// Media Type Selector
|
||||
int selectedMediaTypeIndex = selectedCamera->m_selectedMediaTypeIndex;
|
||||
int selectedMediaTypeIndex = selectedCamera->GetSelectedMediaTypeIndex();
|
||||
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(
|
||||
"Media Type", selectedCamera->m_mediaTypeInfos.at(selectedMediaTypeIndex).description.c_str()
|
||||
);
|
||||
@@ -103,9 +108,9 @@ namespace overlay::windows {
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
if (selectedMediaTypeIndexChanged && selectedMediaTypeIndex != selectedCamera->m_selectedMediaTypeIndex) {
|
||||
if (selectedMediaTypeIndexChanged && selectedMediaTypeIndex != selectedCamera->GetSelectedMediaTypeIndex()) {
|
||||
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
|
||||
@@ -114,13 +119,14 @@ namespace overlay::windows {
|
||||
if (ImGui::Checkbox("Auto##MediaType", &isAutoMediaType)) {
|
||||
selectedCamera->m_useAutoMediaType = isAutoMediaType;
|
||||
if (isAutoMediaType) {
|
||||
selectedCamera->ChangeMediaType(selectedCamera->m_pAutoMediaType);
|
||||
selectedCamera->RequestMediaType(selectedCamera->m_pAutoMediaType);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw mode
|
||||
int selectedDrawModeIndex = selectedCamera->m_drawMode;
|
||||
if (ImGui::BeginCombo("Draw Mode", DRAW_MODE_LABELS[selectedCamera->m_drawMode].c_str())) {
|
||||
const auto drawMode = selectedCamera->m_drawMode.load();
|
||||
int selectedDrawModeIndex = drawMode;
|
||||
if (ImGui::BeginCombo("Draw Mode", DRAW_MODE_LABELS[drawMode].c_str())) {
|
||||
for (size_t i = 0; i < DRAW_MODE_SIZE; i++) {
|
||||
const bool is_selected = (selectedDrawModeIndex == (int) i);
|
||||
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"
|
||||
);
|
||||
|
||||
if (selectedDrawModeIndex != selectedCamera->m_drawMode) {
|
||||
selectedCamera->m_drawMode = (LocalCameraDrawMode)selectedDrawModeIndex;
|
||||
if (selectedDrawModeIndex != selectedCamera->m_drawMode.load()) {
|
||||
selectedCamera->m_drawMode.store((LocalCameraDrawMode)selectedDrawModeIndex);
|
||||
selectedCamera->UpdateDrawRect();
|
||||
}
|
||||
|
||||
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::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
|
||||
ImGui::Separator();
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace overlay::windows {
|
||||
this->texture_width = 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)
|
||||
ImVec2 xy;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
@@ -103,6 +102,13 @@ namespace rawinput {
|
||||
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 {
|
||||
HANDLE handle;
|
||||
_HIDP_CAPS caps;
|
||||
@@ -125,11 +131,12 @@ namespace rawinput {
|
||||
|
||||
std::vector<std::vector<bool>> button_states;
|
||||
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;
|
||||
|
||||
// key: usage page, link collection
|
||||
// value: number of buttons for that key (combine ranges and nonranges)
|
||||
std::map<std::pair<USAGE, ULONG>, ULONG> button_usage_pages;
|
||||
std::vector<HIDButtonInputGroup> button_input_groups;
|
||||
std::vector<CHAR> output_report;
|
||||
std::vector<USAGE> button_output_usages;
|
||||
std::vector<USAGE> button_output_usages_off;
|
||||
|
||||
std::vector<float> value_states;
|
||||
std::vector<LONG> value_states_raw;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#include "rawinput.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdarg>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -9,7 +13,6 @@
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "external/robin_hood.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
@@ -169,9 +172,8 @@ void rawinput::RawInputManager::input_hwnd_create() {
|
||||
});
|
||||
|
||||
// wait for window creation being done
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
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::vector<bool>> button_states;
|
||||
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++) {
|
||||
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;
|
||||
|
||||
// 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 [{}, {}]",
|
||||
device_name,
|
||||
button_caps.Range.UsageMin,
|
||||
@@ -467,11 +471,30 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
||||
}
|
||||
|
||||
// fill vectors
|
||||
const size_t cap_index = button_caps_list.size();
|
||||
button_caps_list.emplace_back(button_caps);
|
||||
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_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
|
||||
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;
|
||||
|
||||
// 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 [{}, {}]",
|
||||
device_name,
|
||||
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_up = std::move(button_up);
|
||||
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_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_raw = std::move(value_states_raw);
|
||||
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) {
|
||||
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(
|
||||
(HRAWINPUT) lParam,
|
||||
RID_INPUT,
|
||||
data.get(),
|
||||
data,
|
||||
&data_size,
|
||||
sizeof(RAWINPUTHEADER)) != data_size) {
|
||||
break;
|
||||
@@ -1791,17 +1830,13 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
||||
+ (size_t) hid_report_index * data_hid.dwSizeHid;
|
||||
|
||||
// parse reports
|
||||
for (const auto &pair : device.hidInfo->button_usage_pages) {
|
||||
const auto usage_page = pair.first.first;
|
||||
const auto link_collection = pair.first.second;
|
||||
const auto button_count = pair.second;
|
||||
|
||||
ULONG usages_length = button_count;
|
||||
std::vector<USAGE> usages(static_cast<size_t>(usages_length));
|
||||
for (auto &input_group : device.hidInfo->button_input_groups) {
|
||||
auto &usages = input_group.usages;
|
||||
ULONG usages_length = static_cast<ULONG>(usages.size());
|
||||
if (HidP_GetUsages(
|
||||
HidP_Input,
|
||||
usage_page,
|
||||
link_collection,
|
||||
input_group.usage_page,
|
||||
input_group.link_collection,
|
||||
usages.data(),
|
||||
&usages_length,
|
||||
reinterpret_cast<PHIDP_PREPARSED_DATA>(device.hidInfo->preparsed_data.get()),
|
||||
@@ -1819,29 +1854,22 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
||||
// log_info(
|
||||
// "rawinput",
|
||||
// "processing HID input for device {}, usage page {:x} and link collection {:x} with {} buttons, got {} reports",
|
||||
// device.desc,
|
||||
// usage_page, link_collection, button_count, usages_length);
|
||||
// device.desc, input_group.usage_page,
|
||||
// input_group.link_collection, usages.size(), usages_length);
|
||||
|
||||
// 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_states = device.hidInfo->button_states[cap_num];
|
||||
auto &button_down = device.hidInfo->button_down[cap_num];
|
||||
auto &button_up = device.hidInfo->button_up[cap_num];
|
||||
|
||||
// is this the right usage page and link collection?
|
||||
if (button_caps.UsagePage != usage_page || button_caps.LinkCollection != link_collection) {
|
||||
continue;
|
||||
}
|
||||
auto &new_states = device.hidInfo->button_report_states[cap_num];
|
||||
|
||||
// get button count
|
||||
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
|
||||
if (button_count <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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++) {
|
||||
if (usages[usage_num] < button_caps.Range.UsageMin ||
|
||||
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
|
||||
if (usage < button_count) {
|
||||
new_states[usage] = true;
|
||||
new_states[usage] = 1;
|
||||
}
|
||||
}
|
||||
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;
|
||||
button_states[button_num] = new_states[button_num];
|
||||
button_states[button_num] = new_state;
|
||||
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;
|
||||
button_states[button_num] = new_states[button_num];
|
||||
button_states[button_num] = new_state;
|
||||
button_up[button_num] = input_time;
|
||||
}
|
||||
}
|
||||
@@ -2017,8 +2046,10 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
||||
switch (hid->driver) {
|
||||
case HIDDriver::Default: {
|
||||
|
||||
// allocate report
|
||||
CHAR *report_data = new CHAR[hid->caps.OutputReportByteLength] {};
|
||||
auto &report_data = hid->output_report;
|
||||
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
|
||||
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];
|
||||
|
||||
// determine which buttons to turn on
|
||||
std::vector<USAGE> usage_list;
|
||||
std::vector<USAGE> usage_off_list;
|
||||
usage_list.reserve(button_state_list.size());
|
||||
usage_off_list.reserve(button_state_list.size());
|
||||
usage_list.clear();
|
||||
usage_off_list.clear();
|
||||
for (size_t state_no = 0; state_no < button_state_list.size(); state_no++) {
|
||||
if (button_state_list[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,
|
||||
button_cap.UsagePage,
|
||||
button_cap.LinkCollection,
|
||||
&usage_list[0],
|
||||
usage_list.data(),
|
||||
&usage_list_length,
|
||||
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
||||
report_data,
|
||||
report_data.data(),
|
||||
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
||||
|
||||
// flush report
|
||||
HidD_SetOutputReport(hid->handle, report_data, hid->caps.OutputReportByteLength);
|
||||
memset(report_data, 0, hid->caps.OutputReportByteLength);
|
||||
HidD_SetOutputReport(
|
||||
hid->handle,
|
||||
report_data.data(),
|
||||
hid->caps.OutputReportByteLength);
|
||||
std::fill(report_data.begin(), report_data.end(), 0);
|
||||
}
|
||||
|
||||
// clear the buttons
|
||||
@@ -2061,22 +2093,22 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
||||
HidP_Output,
|
||||
button_cap.UsagePage,
|
||||
button_cap.LinkCollection,
|
||||
&usage_off_list[0],
|
||||
usage_off_list.data(),
|
||||
&usage_off_list_length,
|
||||
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
||||
report_data,
|
||||
report_data.data(),
|
||||
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
||||
|
||||
// flush report
|
||||
DWORD written_bytes = 0;
|
||||
WriteFile(
|
||||
hid->handle,
|
||||
reinterpret_cast<void *>(report_data),
|
||||
report_data.data(),
|
||||
hid->caps.OutputReportByteLength,
|
||||
&written_bytes,
|
||||
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,
|
||||
static_cast<ULONG>(usage_value),
|
||||
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
|
||||
report_data,
|
||||
report_data.data(),
|
||||
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
|
||||
|
||||
// flush report
|
||||
DWORD written_bytes = 0;
|
||||
WriteFile(
|
||||
hid->handle,
|
||||
reinterpret_cast<void *>(report_data),
|
||||
report_data.data(),
|
||||
hid->caps.OutputReportByteLength,
|
||||
&written_bytes,
|
||||
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;
|
||||
WriteFile(
|
||||
hid->handle,
|
||||
reinterpret_cast<void *>(report_data),
|
||||
report_data.data(),
|
||||
hid->caps.OutputReportByteLength,
|
||||
&written_bytes,
|
||||
nullptr
|
||||
);
|
||||
|
||||
// delete report
|
||||
delete[] report_data;
|
||||
|
||||
break;
|
||||
}
|
||||
case HIDDriver::PacDrive: {
|
||||
|
||||
@@ -319,7 +319,8 @@ namespace rawinput::touch {
|
||||
}
|
||||
|
||||
// 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());
|
||||
for (size_t i = 0; i < touch.elements_x.size(); i++) {
|
||||
|
||||
@@ -424,9 +425,15 @@ namespace rawinput::touch {
|
||||
}
|
||||
|
||||
// process touch points
|
||||
std::vector<DWORD> touch_removes;
|
||||
std::vector<TouchPoint> touch_writes;
|
||||
std::vector<DWORD> touch_modifications;
|
||||
thread_local std::vector<DWORD> touch_removes;
|
||||
thread_local std::vector<TouchPoint> touch_writes;
|
||||
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)
|
||||
auto remove_touch_point = [&] (DWORD id) {
|
||||
@@ -581,7 +588,9 @@ namespace rawinput::touch {
|
||||
auto deadline = get_system_milliseconds() - 50;
|
||||
|
||||
// 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();
|
||||
while (touch_it != touch.touch_points.end()) {
|
||||
auto &hid_tp = *touch_it;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#include "reader.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
#include "structuredmessage.h"
|
||||
|
||||
@@ -218,8 +218,6 @@ bool Reader::set_comm_state(DWORD BaudRate) {
|
||||
}
|
||||
|
||||
bool Reader::wait_for_handshake() {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// baud rates
|
||||
DWORD baud_rates[] = { CBR_57600, CBR_38400, CBR_19200, CBR_9600 };
|
||||
|
||||
@@ -270,7 +268,7 @@ bool Reader::wait_for_handshake() {
|
||||
}
|
||||
|
||||
// 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]);
|
||||
@@ -407,7 +405,6 @@ void start_reader_thread(const std::string &port, int id) {
|
||||
READER_THREAD_RUNNING = true;
|
||||
READER_THREADS.push_back(new std::thread([port, id]() {
|
||||
log_info("reader", "{}: starting reader thread", port);
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
while (READER_THREAD_RUNNING) {
|
||||
|
||||
@@ -445,22 +442,21 @@ void start_reader_thread(const std::string &port, int id) {
|
||||
}
|
||||
|
||||
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
|
||||
if (READER_THREAD_RUNNING)
|
||||
timer.sleep(5000);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
|
||||
}
|
||||
}));
|
||||
|
||||
// wait for thread to start
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
timer.sleep(10);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
void stop_reader_thread() {
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace nativetouch::inject {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -108,7 +108,7 @@ namespace nativetouch::inject {
|
||||
}
|
||||
|
||||
POINT transformed = position;
|
||||
if (!transform::screen_to_game(window, &transformed)) {
|
||||
if (!transform::mouse_to_game(window, &transformed)) {
|
||||
end_mouse_contact(window);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace nativetouch::transform {
|
||||
static bool has_active_overlay_transform() {
|
||||
return overlay::OVERLAY != nullptr &&
|
||||
overlay::OVERLAY->get_active() &&
|
||||
overlay::OVERLAY->can_transform_touch_input();
|
||||
overlay::OVERLAY->has_subscreen_touch_transform();
|
||||
}
|
||||
|
||||
static bool transform_overlay_touch_position(POINT *position) {
|
||||
@@ -120,6 +120,25 @@ namespace nativetouch::transform {
|
||||
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
|
||||
Result hardware_to_game(POINT *position) {
|
||||
const auto dedicated_subscreen = is_tdj_dedicated_subscreen(TDJ_SUBSCREEN_WINDOW);
|
||||
|
||||
@@ -12,5 +12,6 @@ namespace nativetouch::transform {
|
||||
bool is_tdj_dedicated_subscreen(HWND window);
|
||||
bool game_to_screen(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);
|
||||
}
|
||||
|
||||
@@ -944,7 +944,7 @@ void touch_get_points(std::vector<TouchPoint> &touch_points, bool overlay) {
|
||||
if (!overlay &&
|
||||
overlay::OVERLAY &&
|
||||
overlay::OVERLAY->get_active() &&
|
||||
!overlay::OVERLAY->can_transform_touch_input() &&
|
||||
!overlay::OVERLAY->has_subscreen_touch_transform() &&
|
||||
ImGui::GetIO().WantCaptureMouse) {
|
||||
|
||||
return;
|
||||
@@ -971,7 +971,7 @@ void touch_get_events(std::vector<TouchEvent> &touch_events, bool overlay) {
|
||||
if (!overlay &&
|
||||
overlay::OVERLAY &&
|
||||
overlay::OVERLAY->get_active() &&
|
||||
!overlay::OVERLAY->can_transform_touch_input() &&
|
||||
!overlay::OVERLAY->has_subscreen_touch_transform() &&
|
||||
ImGui::GetIO().WantCaptureMouse) {
|
||||
|
||||
TOUCH_EVENTS.reset();
|
||||
|
||||
Reference in New Issue
Block a user