Compare commits

...

4 Commits

Author SHA1 Message Date
bicarus c590b87cff jb: add t44 support (#845)
With help from certain sea creature.
2026-08-01 01:20:31 -07:00
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
37 changed files with 1539 additions and 364 deletions
+1
View File
@@ -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
+1 -1
View File
@@ -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
+4 -4
View File
@@ -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));
}
}
+3 -3
View File
@@ -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));
}
}
});
+1
View File
@@ -1114,6 +1114,7 @@ namespace avs {
#endif
// jubeat
{"jubeat.dll", 0x2000000},
{"jubeat2019.dll", 0x10000000},
// MUSECA
{"museca.dll", 0xC000000},
+1
View File
@@ -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)
{
+10 -8
View File
@@ -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
+36 -11
View File
@@ -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 &current);
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();
};
}
+16
View File
@@ -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,
+3
View File
@@ -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,
+2 -4
View File
@@ -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()) {
+1
View File
@@ -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");
+336
View File
@@ -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
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#if SPICE64
namespace games::jb {
void bi2x_hook_init();
}
#endif
+18
View File
@@ -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() {
+40 -26
View File
@@ -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);
+2 -4
View File
@@ -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);
}
}
+4 -3
View File
@@ -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",
+8 -1
View File
@@ -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) {
+9 -10
View File
@@ -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");
+8
View File
@@ -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;
+11
View File
@@ -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);
}
+9 -7
View File
@@ -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);
};
+18 -2
View File
@@ -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;
}
+2
View File
@@ -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
+26 -10
View File
@@ -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();
+1 -1
View File
@@ -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;
+12 -5
View File
@@ -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;
+86 -57
View File
@@ -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: {
+14 -5
View File
@@ -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;
+7 -11
View File
@@ -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() {
+2 -2
View File
@@ -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;
}
+20 -1
View File
@@ -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);
+1
View File
@@ -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);
}
+2 -2
View File
@@ -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();