346 lines
14 KiB
C++
346 lines
14 KiB
C++
#include "poke.h"
|
|
|
|
#include <thread>
|
|
|
|
#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;
|
|
|
|
// <KeypadID>/<Key> mapped to 1080p coordinate space
|
|
static const std::unordered_map<std::string, int> 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<std::string, int> 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<InitializeTouchInjection_t>(
|
|
USER32_INSTANCE, "InitializeTouchInjection");
|
|
pInjectTouchInput = libutils::try_proc<InjectTouchInput_t>(
|
|
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<TouchPoint> *touch_points) {
|
|
int i = 0;
|
|
for (auto &touch : *touch_points) {
|
|
emulate_native_touch(touch, true);
|
|
}
|
|
}
|
|
|
|
void clear_native_touch_points(std::vector<TouchPoint> *touch_points) {
|
|
for (auto &touch : *touch_points) {
|
|
emulate_native_touch(touch, false);
|
|
}
|
|
touch_points->clear();
|
|
}
|
|
#endif
|
|
|
|
void clear_touch_points(std::vector<TouchPoint> *touch_points) {
|
|
std::vector<DWORD> 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<TouchPoint> touch_points;
|
|
std::vector<uint16_t> 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");
|
|
}
|
|
}
|