#include "poke.h" #include #include "windows.h" #include "cfg/screen_resize.h" #include "games/io.h" #include "games/iidx/iidx.h" #include "hooks/graphics/graphics.h" #include "launcher/shutdown.h" #include "misc/eamuse.h" #include "overlay/overlay.h" #include "overlay/windows/generic_sub.h" #include "rawinput/rawinput.h" #include "touch/touch.h" #include "util/libutils.h" #include "util/logging.h" #define POKE_NATIVE_TOUCH 0 #if POKE_NATIVE_TOUCH static HINSTANCE USER32_INSTANCE = nullptr; typedef BOOL (WINAPI *InitializeTouchInjection_t)(UINT32, DWORD); typedef BOOL (WINAPI *InjectTouchInput_t)(UINT32, POINTER_TOUCH_INFO*); static InitializeTouchInjection_t pInitializeTouchInjection = nullptr; static InjectTouchInput_t pInjectTouchInput = nullptr; #endif namespace poke { static std::thread *THREAD = nullptr; static bool THREAD_RUNNING = false; struct KeypadMapping { char character; uint16_t state; }; static KeypadMapping KEYPAD_MAPPINGS[] = { { '0', 1 << EAM_IO_KEYPAD_0 }, { '1', 1 << EAM_IO_KEYPAD_1 }, { '2', 1 << EAM_IO_KEYPAD_2 }, { '3', 1 << EAM_IO_KEYPAD_3 }, { '4', 1 << EAM_IO_KEYPAD_4 }, { '5', 1 << EAM_IO_KEYPAD_5 }, { '6', 1 << EAM_IO_KEYPAD_6 }, { '7', 1 << EAM_IO_KEYPAD_7 }, { '8', 1 << EAM_IO_KEYPAD_8 }, { '9', 1 << EAM_IO_KEYPAD_9 }, { 'A', 1 << EAM_IO_KEYPAD_00 }, // Touch panel keypad does not have the decimal key. // This will be used for toggling the keypad instead { 'D', 1 << EAM_IO_KEYPAD_DECIMAL }, }; const int IIDX_KEYPAD_BUTTON_SIZE = 90; const int IIDX_KEYPAD_GAP = 15; const int IIDX_KEYPAD_TOP = 570; const int IIDX_KEYPAD_LEFT_1P = 90; const int IIDX_KEYPAD_LEFT_2P = 1530; // / mapped to 1080p coordinate space static const std::unordered_map IIDX_KEYPAD_POSITION_X { {"0/0", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)}, {"0/1", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)}, {"0/2", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)}, {"0/3", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 2.5 + IIDX_KEYPAD_GAP * 2)}, {"0/4", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)}, {"0/5", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)}, {"0/6", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 2.5 + IIDX_KEYPAD_GAP * 2)}, {"0/7", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)}, {"0/8", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)}, {"0/9", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 2.5 + IIDX_KEYPAD_GAP * 2)}, {"0/A", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)}, {"0/D", 60}, {"1/0", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)}, {"1/1", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)}, {"1/2", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)}, {"1/3", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 2.5 + IIDX_KEYPAD_GAP * 2)}, {"1/4", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)}, {"1/5", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)}, {"1/6", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 2.5 + IIDX_KEYPAD_GAP * 2)}, {"1/7", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)}, {"1/8", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)}, {"1/9", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 2.5 + IIDX_KEYPAD_GAP * 2)}, {"1/A", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)}, {"1/D", 1920 - 60}, }; static const std::unordered_map IIDX_KEYPAD_POSITION_Y { {"0/0", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 3 + IIDX_KEYPAD_BUTTON_SIZE * 3.5)}, {"0/1", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 2 + IIDX_KEYPAD_BUTTON_SIZE * 2.5)}, {"0/2", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 2 + IIDX_KEYPAD_BUTTON_SIZE * 2.5)}, {"0/3", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 2 + IIDX_KEYPAD_BUTTON_SIZE * 2.5)}, {"0/4", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP + IIDX_KEYPAD_BUTTON_SIZE * 1.5)}, {"0/5", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP + IIDX_KEYPAD_BUTTON_SIZE * 1.5)}, {"0/6", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP + IIDX_KEYPAD_BUTTON_SIZE * 1.5)}, {"0/7", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_BUTTON_SIZE * 0.5)}, {"0/8", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_BUTTON_SIZE * 0.5)}, {"0/9", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_BUTTON_SIZE * 0.5)}, {"0/A", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 3 + IIDX_KEYPAD_BUTTON_SIZE * 3.5)}, {"0/D", 50}, {"1/0", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 3 + IIDX_KEYPAD_BUTTON_SIZE * 3.5)}, {"1/1", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 2 + IIDX_KEYPAD_BUTTON_SIZE * 2.5)}, {"1/2", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 2 + IIDX_KEYPAD_BUTTON_SIZE * 2.5)}, {"1/3", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 2 + IIDX_KEYPAD_BUTTON_SIZE * 2.5)}, {"1/4", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP + IIDX_KEYPAD_BUTTON_SIZE * 1.5)}, {"1/5", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP + IIDX_KEYPAD_BUTTON_SIZE * 1.5)}, {"1/6", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP + IIDX_KEYPAD_BUTTON_SIZE * 1.5)}, {"1/7", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_BUTTON_SIZE * 0.5)}, {"1/8", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_BUTTON_SIZE * 0.5)}, {"1/9", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_BUTTON_SIZE * 0.5)}, {"1/A", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 3 + IIDX_KEYPAD_BUTTON_SIZE * 3.5)}, {"1/D", 50}, }; #if POKE_NATIVE_TOUCH void initialize_native_touch_library() { if (USER32_INSTANCE == nullptr) { USER32_INSTANCE = libutils::load_library("user32.dll"); } pInitializeTouchInjection = libutils::try_proc( USER32_INSTANCE, "InitializeTouchInjection"); pInjectTouchInput = libutils::try_proc( USER32_INSTANCE, "InjectTouchInput"); } void emulate_native_touch(TouchPoint tp, bool is_down) { if (pInjectTouchInput == nullptr) { return; } POINTER_TOUCH_INFO contact; BOOL bRet = TRUE; const int contact_offset = 2; memset(&contact, 0, sizeof(POINTER_TOUCH_INFO)); contact.pointerInfo.pointerType = PT_TOUCH; contact.pointerInfo.pointerId = 0; contact.pointerInfo.ptPixelLocation.x = tp.x; contact.pointerInfo.ptPixelLocation.y = tp.y; if (is_down) { contact.pointerInfo.pointerFlags = POINTER_FLAG_DOWN | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT; } else { contact.pointerInfo.pointerFlags = POINTER_FLAG_UP; } contact.pointerInfo.dwTime = 0; contact.pointerInfo.PerformanceCount = 0; contact.touchFlags = TOUCH_FLAG_NONE; contact.touchMask = TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE; contact.orientation = 0; contact.pressure = 1024; contact.rcContact.top = tp.y - contact_offset; contact.rcContact.bottom = tp.y + contact_offset; contact.rcContact.left = tp.x - contact_offset; contact.rcContact.right = tp.x + contact_offset; bRet = InjectTouchInput(1, &contact); if (!bRet) { log_warning("poke", "error injecting native touch {}: ({}, {}) error: {}", is_down ? "down" : "up", tp.x, tp.y, GetLastError()); } } void emulate_native_touch_points(std::vector *touch_points) { int i = 0; for (auto &touch : *touch_points) { emulate_native_touch(touch, true); } } void clear_native_touch_points(std::vector *touch_points) { for (auto &touch : *touch_points) { emulate_native_touch(touch, false); } touch_points->clear(); } #endif void clear_touch_points(std::vector *touch_points) { std::vector touch_ids; for (auto &touch : *touch_points) { touch_ids.emplace_back(touch.id); } touch_remove_points(&touch_ids); touch_points->clear(); } void enable() { // check if already running if (THREAD) return; // create new thread THREAD_RUNNING = true; THREAD = new std::thread([] { // log log_info("poke", "enabled"); std::vector touch_points; std::vector last_keypad_state = {0, 0}; #if POKE_NATIVE_TOUCH bool use_native = games::iidx::NATIVE_TOUCH; if (use_native) { initialize_native_touch_library(); if (pInitializeTouchInjection != nullptr) { pInitializeTouchInjection(1, TOUCH_FEEDBACK_NONE); } } #else bool use_native = false; #endif // set variable to false to stop while (THREAD_RUNNING) { // clean up touch from last frame if (touch_points.size() > 0) { #if POKE_NATIVE_TOUCH if (use_native) { clear_native_touch_points(&touch_points); } else { clear_touch_points(&touch_points); } #else clear_touch_points(&touch_points); #endif } for (int unit = 0; unit < 2; unit++) { // get keypad state auto state = eamuse_get_keypad_state(unit); if (state != 0) { // add keys for (auto &mapping : KEYPAD_MAPPINGS) { if (state & mapping.state) { if (last_keypad_state[unit] & mapping.state) { // log_warning("poke", "ignoring hold {} {}", unit, mapping.character); continue; } std::string handle = fmt::format("{0}/{1}", unit, mapping.character); auto x_iter = IIDX_KEYPAD_POSITION_X.find(handle); auto y_iter = IIDX_KEYPAD_POSITION_Y.find(handle); if (x_iter != IIDX_KEYPAD_POSITION_X.end() && y_iter != IIDX_KEYPAD_POSITION_Y.end()) { DWORD touch_id = (DWORD)(0xFFFFFF * unit + mapping.character); float x = x_iter->second / 1920.0; float y = y_iter->second / 1080.0; if (use_native) { x *= rawinput::TOUCHSCREEN_RANGE_X; y *= rawinput::TOUCHSCREEN_RANGE_Y; } else if (GRAPHICS_IIDX_WSUB) { // Scale to windowed subscreen x *= GRAPHICS_IIDX_WSUB_WIDTH; y *= GRAPHICS_IIDX_WSUB_HEIGHT; } else if (GENERIC_SUB_WINDOW_FULLSIZE || !overlay::OVERLAY->get_active()) { // Overlay is not present, scale to main screen if (GRAPHICS_WINDOWED) { x *= SPICETOUCH_TOUCH_WIDTH; y *= SPICETOUCH_TOUCH_HEIGHT; } else { x *= ImGui::GetIO().DisplaySize.x; y *= ImGui::GetIO().DisplaySize.y; } } else { // Overlay subscreen x = (GENERIC_SUB_WINDOW_X + x * GENERIC_SUB_WINDOW_WIDTH); y = (GENERIC_SUB_WINDOW_Y + y * GENERIC_SUB_WINDOW_HEIGHT); // Scale to window size ratio if (GRAPHICS_WINDOWED) { x *= SPICETOUCH_TOUCH_WIDTH / ImGui::GetIO().DisplaySize.x; y *= SPICETOUCH_TOUCH_HEIGHT / ImGui::GetIO().DisplaySize.y; } } TouchPoint tp { .id = touch_id, .x = (LONG)x, .y = (LONG)y, .mouse = true, }; touch_points.emplace_back(tp); // log_warning("poke", "coords: {} {}", to_string(tp.x), to_string(tp.y)); } } } // for all keys } // if state != 0 last_keypad_state[unit] = state; } // for all units if (touch_points.size() > 0) { #if POKE_NATIVE_TOUCH if (use_native) { emulate_native_touch_points(&touch_points); } else { touch_write_points(&touch_points); } #else touch_write_points(&touch_points); #endif } // slow down Sleep(50); } return nullptr; }); } void disable() { if (!THREAD) { return; } // stop old thread THREAD_RUNNING = false; THREAD->join(); // delete thread delete THREAD; THREAD = nullptr; // log log_info("poke", "disabled"); } }