mirror of
https://github.com/pumpitupdev/pumptools.git
synced 2026-09-28 09:47:55 +03:00
Version 1.08 with public source code release.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
#define LOG_MODULE "patch-amixer-block"
|
||||
|
||||
#include "capnhook/hook/lib.h"
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
typedef int (*system_t)(const char* command);
|
||||
typedef int (*execv_t)(const char* path, char* const argv[]);
|
||||
|
||||
static bool patch_axmier_block_initialized;
|
||||
static system_t patch_amixer_block_real_system;
|
||||
static execv_t patch_amixer_block_real_execv;
|
||||
|
||||
// for fst and fex
|
||||
int system(const char* command)
|
||||
{
|
||||
if (!patch_amixer_block_real_system) {
|
||||
patch_amixer_block_real_system = (system_t) cnh_lib_get_func_addr("system");
|
||||
}
|
||||
|
||||
if (patch_axmier_block_initialized) {
|
||||
if (util_str_starts_with(command, "amixer")) {
|
||||
log_info("Blocking call to amixer: %s", command);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return patch_amixer_block_real_system(command);
|
||||
}
|
||||
|
||||
// for f2 and prime (yes, they figured using system() isn't a great idea...)
|
||||
int execv(const char* path, char* const argv[])
|
||||
{
|
||||
if (!patch_amixer_block_real_execv) {
|
||||
patch_amixer_block_real_execv = (execv_t) cnh_lib_get_func_addr("execv");
|
||||
}
|
||||
|
||||
if (patch_axmier_block_initialized) {
|
||||
if (util_str_starts_with(path, "/usr/bin/amixer")) {
|
||||
log_info("Blocking call to amixer: %s", path);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return patch_amixer_block_real_execv(path, argv);
|
||||
}
|
||||
|
||||
void patch_axmier_block_init(void)
|
||||
{
|
||||
patch_axmier_block_initialized = true;
|
||||
log_info("Initialized");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Block the system call to amixer which messes with the volume levels
|
||||
* (relevant for Fiesta and up)
|
||||
*/
|
||||
#ifndef PATCH_AMIXER_BLOCK_H
|
||||
#define PATCH_AMIXER_BLOCK_H
|
||||
|
||||
/**
|
||||
* Initialize patch module
|
||||
*/
|
||||
void patch_axmier_block_init(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,147 @@
|
||||
#define LOG_MODULE "patch-asound-fix"
|
||||
|
||||
#include <errno.h>
|
||||
#include <grp.h>
|
||||
|
||||
#include "capnhook/hook/lib.h"
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/mem.h"
|
||||
#include "util/str.h"
|
||||
|
||||
typedef int (*getgrnam_r_t)(const char *name, struct group *grp, char *buf, size_t buflen, struct group **result);
|
||||
|
||||
static getgrnam_r_t patch_asound_fix_real_getgrnam_r;
|
||||
|
||||
static char** patch_asound_fix_split_user_list_str(const char* user_list_str)
|
||||
{
|
||||
char* user_list_str_cpy = util_str_dup(user_list_str);
|
||||
char** user_list = util_xmalloc(sizeof(char*));
|
||||
size_t len_list = 1;
|
||||
|
||||
char* ptr = strtok(user_list_str_cpy, ",");
|
||||
|
||||
while (ptr != NULL) {
|
||||
// last token will likely read beyond newline to next : token for next group entry
|
||||
char* new_line_char = strstr(ptr, "\n");
|
||||
|
||||
if (new_line_char) {
|
||||
*new_line_char = '\0';
|
||||
}
|
||||
|
||||
len_list++;
|
||||
user_list = util_xrealloc(user_list, len_list * sizeof(char*));
|
||||
|
||||
user_list[len_list - 2] = util_str_dup(ptr);
|
||||
user_list[len_list - 1] = NULL;
|
||||
|
||||
ptr = strtok(NULL, ",");
|
||||
}
|
||||
|
||||
free(user_list_str_cpy);
|
||||
|
||||
return user_list;
|
||||
}
|
||||
|
||||
static char* patch_asound_fix_user_list_to_str(char** user_list)
|
||||
{
|
||||
char** ptr = user_list;
|
||||
char* str = util_str_dup("\0");
|
||||
|
||||
while (*ptr != NULL) {
|
||||
char* mrg = util_str_merge(str, *ptr);
|
||||
free(str);
|
||||
str = util_str_merge(mrg, ",");
|
||||
free(mrg);
|
||||
|
||||
ptr++;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
int getgrnam_r(const char *name, struct group *grp, char *buf, size_t buflen, struct group **result)
|
||||
{
|
||||
if (!patch_asound_fix_real_getgrnam_r) {
|
||||
patch_asound_fix_real_getgrnam_r = (getgrnam_r_t) cnh_lib_get_func_addr("getgrnam_r");
|
||||
}
|
||||
|
||||
// We expect to use the standard "audio" group here, otherwise detection of this is not possible. Intercept
|
||||
// and execute the following re-implementation of the reading logic from file. I don't know if getgrnam_r usually
|
||||
// reads from a cached version of /etc/group but I suppose that doesn't matter here.
|
||||
if (!strcmp(name, "audio")) {
|
||||
log_debug("Trapping getgrnam_r for audio group of libasound");
|
||||
|
||||
FILE* file = fopen("/etc/group", "r");
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
size_t file_size = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
char* buffer = (char*) util_xmalloc(file_size);
|
||||
|
||||
if (fread(buffer, file_size, 1, file) != 1) {
|
||||
log_error("Reading /etc/group file failed: %s", strerror(errno));
|
||||
free(buffer);
|
||||
*result = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
char* str_audio_pos = strstr(buffer, name);
|
||||
|
||||
if (str_audio_pos == NULL) {
|
||||
log_error("Could not find 'audio' group in /etc/group required by libasound likely for defaults.pcm.ipc_gid"
|
||||
" from asound.conf. Make sure the group exists.");
|
||||
free(buffer);
|
||||
*result = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
char* tok = strtok(str_audio_pos, ":");
|
||||
uint8_t cnt = 0;
|
||||
|
||||
while (tok != NULL) {
|
||||
switch (cnt) {
|
||||
case 0:
|
||||
grp->gr_name = util_str_dup(tok);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
grp->gr_passwd = util_str_dup(tok);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
grp->gr_gid = strtol(tok, NULL, 10);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
grp->gr_mem = patch_asound_fix_split_user_list_str(tok);
|
||||
break;
|
||||
|
||||
default:
|
||||
log_error("audio group entry format in /etc/group invalid. Check your /etc/group file!");
|
||||
free(buffer);
|
||||
*result = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
cnt++;
|
||||
tok = strtok(NULL, ":");
|
||||
}
|
||||
|
||||
char* grp_mem_str = patch_asound_fix_user_list_to_str(grp->gr_mem);
|
||||
log_debug("Resulting group struct: %s %s %d %s", grp->gr_name, grp->gr_passwd, grp->gr_gid, grp_mem_str);
|
||||
free(grp_mem_str);
|
||||
|
||||
*result = grp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return patch_asound_fix_real_getgrnam_r(name, grp, buf, buflen, result);
|
||||
}
|
||||
|
||||
void patch_asound_fix_init()
|
||||
{
|
||||
log_info("Initialized");
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Fix a quirk with getgrnam_r in the asound library always failing when loading the asound.conf file and reading the
|
||||
* /etc/group file. For some reason, it cannot find the audio group entry even if it is available and can be read
|
||||
* by other applications just fine.
|
||||
*
|
||||
* Your typical asound.conf, which is loaded by libasound in a application that uses it, contains the following line:
|
||||
* defaults.pcm.ipc_gid audio
|
||||
*
|
||||
* Finding documentation about what this stuff does it difficult but judging by the name, this seems to be related to
|
||||
* setting up shared memory IPC for reading and writing data to the sound driver.
|
||||
* For some reason, this "fails" when the function getgrnam_r is called by libasound. The function itself returns
|
||||
* successful but with no result. This function reads the entry from /etc/group. libsound queries the "audio" group
|
||||
* in that case and even if the entry exists, the result is empty for some unknown reason.
|
||||
*
|
||||
* This module fixes that issue.
|
||||
*/
|
||||
#ifndef PATCH_ASOUND_FIX_H
|
||||
#define PATCH_ASOUND_FIX_H
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*/
|
||||
void patch_asound_fix_init();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,66 @@
|
||||
#define LOG_MODULE "patch-blacklist-url"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "capnhook/hook/lib.h"
|
||||
|
||||
#include "util/list.h"
|
||||
#include "util/log.h"
|
||||
#include "util/mem.h"
|
||||
|
||||
struct patch_blacklist_url_list_elem {
|
||||
struct util_list_node head;
|
||||
const char* url;
|
||||
};
|
||||
|
||||
typedef int (*curl_easy_setopt_t)(int* handle, int option, void* param);
|
||||
|
||||
static bool patch_blacklist_url_initialized;
|
||||
static curl_easy_setopt_t patch_blacklist_url_real_curl_easy_setopt;
|
||||
static struct util_list patch_blacklist_url_list;
|
||||
|
||||
int curl_easy_setopt(int* handle, int option, void* param)
|
||||
{
|
||||
if (!patch_blacklist_url_real_curl_easy_setopt) {
|
||||
patch_blacklist_url_real_curl_easy_setopt = (curl_easy_setopt_t) cnh_lib_get_func_addr("curl_easy_setopt");
|
||||
}
|
||||
|
||||
if (patch_blacklist_url_initialized) {
|
||||
|
||||
/* option == 10002 to identify that param has a string */
|
||||
if (option == 10002) {
|
||||
|
||||
struct util_list_node* pos;
|
||||
struct patch_blacklist_url_list_elem* elem;
|
||||
|
||||
for (pos = patch_blacklist_url_list.head; pos != NULL; pos = pos->next) {
|
||||
elem = containerof(pos, struct patch_blacklist_url_list_elem, head);
|
||||
|
||||
if (!memcmp(param, elem->url, strlen(elem->url))) {
|
||||
log_debug("Blocking url %s", elem->url);
|
||||
return patch_blacklist_url_real_curl_easy_setopt(handle, option, (void*) "does-not-exist");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return patch_blacklist_url_real_curl_easy_setopt(handle, option, param);
|
||||
}
|
||||
|
||||
void patch_blacklist_url_init(void)
|
||||
{
|
||||
util_list_init(&patch_blacklist_url_list);
|
||||
patch_blacklist_url_initialized = true;
|
||||
log_info("Initialized");
|
||||
}
|
||||
|
||||
void patch_blacklist_url_add(const char* url)
|
||||
{
|
||||
struct patch_blacklist_url_list_elem* elem;
|
||||
|
||||
elem = util_xmalloc(sizeof(struct patch_blacklist_url_list_elem));
|
||||
elem->url = strdup(url);
|
||||
|
||||
util_list_append(&patch_blacklist_url_list, &elem->head);
|
||||
log_info("Add url %s to blacklist", url);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Blacklist calls to offical update and game server (Prime and up).
|
||||
*/
|
||||
#ifndef PATCH_BLACKLIST_URL_H
|
||||
#define PATCH_BLACKLIST_URL_H
|
||||
|
||||
/**
|
||||
* Initialize patch module
|
||||
*/
|
||||
void patch_blacklist_url_init(void);
|
||||
|
||||
/**
|
||||
* Add an url to block to the blacklist
|
||||
*
|
||||
* @param url Url to block
|
||||
*/
|
||||
void patch_blacklist_url_add(const char* url);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,36 @@
|
||||
#define LOG_MODULE "block-keyboard-grab"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <X11/Xutil.h>
|
||||
|
||||
#include "capnhook/hook/lib.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
typedef int (*XGrabKeyboard_t)(Display* display, Window window, Bool owner_events, int pointer_mode, int keyboard_mode,
|
||||
Time time);
|
||||
|
||||
static XGrabKeyboard_t _patch_main_loop_real_XGrabKeyboard;
|
||||
|
||||
static bool _patch_block_keyboard_grab_enable;
|
||||
|
||||
int XGrabKeyboard(Display* display, Window window, Bool owner_events, int pointer_mode, int keyboard_mode, Time time)
|
||||
{
|
||||
if (!_patch_main_loop_real_XGrabKeyboard) {
|
||||
_patch_main_loop_real_XGrabKeyboard = (XGrabKeyboard_t) cnh_lib_get_func_addr("XGrabKeyboard");
|
||||
}
|
||||
|
||||
if (_patch_block_keyboard_grab_enable) {
|
||||
log_debug("Block keyboard grabbing");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return _patch_main_loop_real_XGrabKeyboard(display, window, owner_events, pointer_mode, keyboard_mode, time);
|
||||
}
|
||||
|
||||
void patch_block_keyboard_grab_init()
|
||||
{
|
||||
_patch_block_keyboard_grab_enable = true;
|
||||
|
||||
log_info("Initialized");
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef PATCH_BLOCK_KEYBOARD_GRAB_H
|
||||
#define PATCH_BLOCK_KEYBOARD_GRAB_H
|
||||
|
||||
/**
|
||||
* Enable blocking of X11 keyboard grabbing which causes Prime to not go to window mode and behave weird on desktop
|
||||
* machines.
|
||||
*/
|
||||
void patch_block_keyboard_grab_init();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,125 @@
|
||||
#define LOG_MODULE "patch-gfx"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <X11/Xutil.h>
|
||||
|
||||
#include "capnhook/hook/lib.h"
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#include "gfx.h"
|
||||
|
||||
typedef Window (*XCreateWindow_t)(Display *display, Window parent, int x, int y, unsigned int width,
|
||||
unsigned int height, unsigned int border_width, int depth, unsigned int _class, Visual *visual,
|
||||
unsigned long valuemask, XSetWindowAttributes *attributes);
|
||||
typedef Display* (*XOpenDisplay_t)(const char *display_name);
|
||||
typedef XVisualInfo* (*glXChooseVisual_t)(Display *dpy, int screen, int *attribList);
|
||||
|
||||
static bool patch_gfx_force_window = false;
|
||||
|
||||
static bool patch_gfx_initialized;
|
||||
static XCreateWindow_t patch_gfx_real_XCreateWindow;
|
||||
static XOpenDisplay_t patch_gfx_real_XOpenDisplay;
|
||||
static glXChooseVisual_t patch_gfx_real_GlXChooseVisual;
|
||||
|
||||
static char* patch_gfx_attrib_list_to_str(int* attrib_list)
|
||||
{
|
||||
int* ptr = attrib_list;
|
||||
char* str = util_str_dup("");
|
||||
char buf[32];
|
||||
|
||||
while (*ptr != None) {
|
||||
sprintf(buf, "%d, ", *ptr);
|
||||
|
||||
char* tmp = util_str_merge(str, buf);
|
||||
|
||||
free(str);
|
||||
str = tmp;
|
||||
|
||||
ptr++;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
Window XCreateWindow(Display *display, Window parent, int x, int y, unsigned int width, unsigned int height,
|
||||
unsigned int border_width, int depth, unsigned int _class, Visual *visual, unsigned long valuemask,
|
||||
XSetWindowAttributes *attributes)
|
||||
{
|
||||
log_debug("XCreateWindow");
|
||||
|
||||
if (!patch_gfx_real_XCreateWindow) {
|
||||
patch_gfx_real_XCreateWindow = (XCreateWindow_t) cnh_lib_get_func_addr("XCreateWindow");
|
||||
}
|
||||
|
||||
if (patch_gfx_initialized) {
|
||||
|
||||
if (valuemask == 0x80A) {
|
||||
log_info("Patching to enable usage of non nvidia cards.");
|
||||
/* enables usage of non nvidia cards and newer nvidia models */
|
||||
valuemask = 0x280A;
|
||||
}
|
||||
}
|
||||
|
||||
if (patch_gfx_force_window) {
|
||||
log_info("Forcing window mode");
|
||||
attributes->override_redirect = 0;
|
||||
}
|
||||
|
||||
return patch_gfx_real_XCreateWindow(display, parent, x, y, width, height, border_width, depth, _class, visual,
|
||||
valuemask, attributes);
|
||||
}
|
||||
|
||||
Display *XOpenDisplay(const char *display_name)
|
||||
{
|
||||
if (!patch_gfx_real_XOpenDisplay) {
|
||||
patch_gfx_real_XOpenDisplay = (XOpenDisplay_t) cnh_lib_get_func_addr("XOpenDisplay");
|
||||
}
|
||||
|
||||
log_info("XOpenDisplay: %s", display_name);
|
||||
|
||||
Display* res = patch_gfx_real_XOpenDisplay(display_name);
|
||||
|
||||
if (!res) {
|
||||
log_error("XOpenDisplay returned NULL. This might indicate that your environment is not properly setup. Check "
|
||||
" that you have GPU drivers installed and configured properly and that OpenGL hardware acceleration is "
|
||||
" working (use the command line tool \"glxinfo\").");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
XVisualInfo* glXChooseVisual(Display *dpy, int screen, int *attribList)
|
||||
{
|
||||
if (!patch_gfx_real_GlXChooseVisual) {
|
||||
patch_gfx_real_GlXChooseVisual = (glXChooseVisual_t) cnh_lib_get_func_addr("glXChooseVisual");
|
||||
}
|
||||
|
||||
char* attrib_list_str =patch_gfx_attrib_list_to_str(attribList);
|
||||
|
||||
log_info("glXChooseVisual: dpy %p, screen %d, attribList %s", dpy, screen, attrib_list_str);
|
||||
|
||||
free(attrib_list_str);
|
||||
|
||||
XVisualInfo* res = patch_gfx_real_GlXChooseVisual(dpy, screen, attribList);
|
||||
|
||||
if (!res) {
|
||||
log_error("glXChooseVisual returned NULL. It is likely that your current environment does support hardware"
|
||||
"acceleration. Either you do not have any GPU drivers installed or the drivers are not configured "
|
||||
"properly. Fix that and use the command line tool \"glxinfo\" for debugging this.");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void patch_gfx_init()
|
||||
{
|
||||
patch_gfx_initialized = true;
|
||||
log_info("Initialized");
|
||||
}
|
||||
|
||||
void patch_gfx_force_window_mode()
|
||||
{
|
||||
patch_gfx_force_window = true;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Hook module to run the games on non NVIDIA cards
|
||||
*/
|
||||
#ifndef PATCH_GFX_H
|
||||
#define PATCH_GFX_H
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*/
|
||||
void patch_gfx_init();
|
||||
|
||||
/**
|
||||
* Force the game into window mode
|
||||
*/
|
||||
void patch_gfx_force_window_mode();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
#define LOG_MODULE "patch-hasp"
|
||||
|
||||
#include "sec/hasp/old/hasp.h"
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/patch.h"
|
||||
|
||||
static const uint8_t patch_hasp_login_sig[] = {
|
||||
0x00, 0xB8, 0x16, 0x00,
|
||||
0x00, 0x00, 0x8B, 0x4C,
|
||||
0x24, 0x14, 0x8B, 0x74,
|
||||
0x24, 0x10, 0x8B, 0x54
|
||||
};
|
||||
|
||||
static const uint8_t patch_hasp_id_sig[] = {
|
||||
0x53, 0x83, 0xEC, 0x48,
|
||||
0xC7, 0x44, 0x24, 0x10
|
||||
};
|
||||
|
||||
void patch_hasp_init(const uint8_t* key_data, size_t len)
|
||||
{
|
||||
void* func_api_login;
|
||||
void* func_api_logout;
|
||||
void* func_api_decrypt;
|
||||
void* func_api_getid;
|
||||
|
||||
func_api_login = util_patch_find_signiture(patch_hasp_login_sig,
|
||||
sizeof(patch_hasp_login_sig), -16, (void*) 0x80c0000, (void*) 0x80f0000,
|
||||
16);
|
||||
|
||||
if (!func_api_login) {
|
||||
log_error("Could not find ApiLogin address");
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug("ApiLogin at %p", func_api_login);
|
||||
|
||||
func_api_logout = (void*) (((size_t) func_api_login) - 0x130);
|
||||
log_debug("assuming ApiLogout at %p", func_api_logout);
|
||||
|
||||
func_api_decrypt = (void*) (((size_t) func_api_login) - 0x200);
|
||||
log_debug("assuming ApiDecrypt at %p", func_api_decrypt);
|
||||
|
||||
func_api_getid = util_patch_find_signiture(patch_hasp_id_sig,
|
||||
sizeof(patch_hasp_id_sig), 0, (void*) 0x8060000, (void*) 0x8090000,
|
||||
16);
|
||||
|
||||
if (!func_api_getid) {
|
||||
log_error("Could not find ApiGetid address");
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug("ApiGetid at %p", func_api_getid);
|
||||
|
||||
util_patch_function((uintptr_t) func_api_login, sec_hasp_api_login);
|
||||
util_patch_function((uintptr_t) func_api_logout, sec_hasp_api_logout);
|
||||
util_patch_function((uintptr_t) func_api_decrypt, sec_hasp_api_decrypt);
|
||||
util_patch_function((uintptr_t) func_api_getid, sec_hasp_api_getid);
|
||||
|
||||
sec_hasp_init(key_data, len);
|
||||
|
||||
log_info("Initialized");
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Patch module to install the dongle emulator for a hasp dongle
|
||||
*/
|
||||
#ifndef PATCH_HASP_H
|
||||
#define PATCH_HASP_H
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*
|
||||
* @param key_data Pointer to loaded key data (key table)
|
||||
* @param len Length of the buffer
|
||||
*/
|
||||
void patch_hasp_init(const uint8_t* key_data, size_t len);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
#define LOG_MODULE "patch-hasp"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "sec/hasp/server.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static pthread_t patch_hasp_thread;
|
||||
|
||||
static void* patch_hasp_thread_run(void* args)
|
||||
{
|
||||
log_info("Running hasp server thread");
|
||||
|
||||
sec_hasp_server_run();
|
||||
|
||||
log_info("Finished hasp server thread");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void patch_hasp_init(const uint8_t* key_data, size_t len)
|
||||
{
|
||||
sec_hasp_server_init(key_data, len);
|
||||
|
||||
/* Run the daemon in another thread */
|
||||
pthread_create(&patch_hasp_thread, NULL,
|
||||
patch_hasp_thread_run, NULL);
|
||||
|
||||
log_info("Initialized");
|
||||
}
|
||||
|
||||
void patch_hasp_shutdown(void)
|
||||
{
|
||||
log_info("Shutting down");
|
||||
|
||||
sec_hasp_server_shutdown();
|
||||
|
||||
pthread_join(patch_hasp_thread_run, NULL);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Patch module to install the dongle emulator for a hasp dongle (runs a local server)
|
||||
*/
|
||||
#ifndef PATCH_HASP2_H
|
||||
#define PATCH_HASP2_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Initialize the patch module and run the hasp server
|
||||
*
|
||||
* @param key_data Pointer to loaded key data (attributes and key table)
|
||||
* @param len Length of the buffer
|
||||
*/
|
||||
void patch_hasp_init(const uint8_t* key_data, size_t len);
|
||||
|
||||
/**
|
||||
* Shutdown and remove the emulator
|
||||
*/
|
||||
void patch_hasp_shutdown(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,257 @@
|
||||
#define LOG_MODULE "patch-hdd-check"
|
||||
|
||||
#include <assert.h>
|
||||
#include <linux/hdreg.h>
|
||||
#include <pthread.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "capnhook/hook/filehook.h"
|
||||
#include "capnhook/hook/iohook.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static const uint8_t* patch_hdd_check_boot_area;
|
||||
|
||||
static int patch_hdd_check_fd = -1;
|
||||
static FILE* patch_hdd_check_file = NULL;
|
||||
static size_t patch_hdd_check_boot_area_size;
|
||||
static size_t patch_hdd_check_boot_area_fp;
|
||||
|
||||
static enum cnh_result patch_hdd_check_filehook(struct cnh_filehook_irp *irp)
|
||||
{
|
||||
enum cnh_result result;
|
||||
|
||||
result = CNH_RESULT_SUCCESS;
|
||||
|
||||
assert(irp);
|
||||
|
||||
if (irp->op != CNH_FILEHOOK_IRP_OP_OPEN && (!patch_hdd_check_file || irp->file != patch_hdd_check_file)) {
|
||||
return cnh_filehook_invoke_next(irp);
|
||||
}
|
||||
|
||||
switch (irp->op) {
|
||||
case CNH_FILEHOOK_IRP_OP_OPEN:
|
||||
{
|
||||
if (!strcmp(irp->open_filename, "/dev/hdd")) {
|
||||
patch_hdd_check_file = cnh_filehook_open_dummy_file_handle();
|
||||
irp->file = patch_hdd_check_file;
|
||||
|
||||
log_debug("/dev/hdd fopened: %p", patch_hdd_check_file);
|
||||
} else {
|
||||
return cnh_filehook_invoke_next(irp);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_READ:
|
||||
{
|
||||
uint32_t len;
|
||||
|
||||
log_debug("fread, pos 0x%X, bytes %d", patch_hdd_check_boot_area_fp, irp->read.nbytes);
|
||||
|
||||
/* read up to eof, only */
|
||||
if (patch_hdd_check_boot_area_fp + irp->read.nbytes < patch_hdd_check_boot_area_size) {
|
||||
len = irp->read.nbytes;
|
||||
} else {
|
||||
len = patch_hdd_check_boot_area_size - patch_hdd_check_boot_area_fp;
|
||||
}
|
||||
|
||||
memcpy(irp->read.bytes, patch_hdd_check_boot_area + patch_hdd_check_boot_area_fp, len);
|
||||
|
||||
patch_hdd_check_boot_area_fp += len;
|
||||
irp->read.pos = len;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_WRITE:
|
||||
{
|
||||
// Apparently, Prime starts writing to /dev/hdd, stub
|
||||
log_debug("fwrite");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_SEEK:
|
||||
{
|
||||
/* TODO this is not verified to work, yet
|
||||
not needed on older games as it seems (zero for example)
|
||||
patch seeking for CRED file so we can avoid a huge hdd file */
|
||||
if (irp->seek_offset == 0x1F70000) {
|
||||
log_debug("fseek: fake for hdd (0x1F70000)");
|
||||
irp->seek_offset = 1024;
|
||||
irp->seek_origin = SEEK_SET;
|
||||
} else if (irp->seek_offset == 0x1F70800) {
|
||||
log_debug("fseek: fake for hdd (0x1F70800)");
|
||||
irp->seek_offset = 1024 + 2048;
|
||||
irp->seek_origin = SEEK_SET;
|
||||
}
|
||||
|
||||
switch (irp->seek_origin) {
|
||||
case SEEK_SET:
|
||||
patch_hdd_check_boot_area_fp = (size_t) irp->seek_offset;
|
||||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
patch_hdd_check_boot_area_fp += irp->seek_offset;
|
||||
break;
|
||||
|
||||
case SEEK_END:
|
||||
patch_hdd_check_boot_area_fp = patch_hdd_check_boot_area_size;
|
||||
patch_hdd_check_boot_area_fp += irp->seek_offset;
|
||||
break;
|
||||
|
||||
default:
|
||||
log_die("Unhandled origin %d", irp->seek_origin);
|
||||
break;
|
||||
}
|
||||
|
||||
log_debug("fseek, offset 0x%X, origin %d", irp->seek_offset, irp->seek_origin);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_CLOSE:
|
||||
patch_hdd_check_file = NULL;
|
||||
|
||||
log_debug("/dev/hdd fclosed");
|
||||
|
||||
/* Actually close dummy handle */
|
||||
return cnh_filehook_invoke_next(irp);
|
||||
|
||||
default:
|
||||
log_warn("Unhandled file op: %d", irp->op);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static enum cnh_result patch_hdd_check_iohook(struct cnh_iohook_irp *irp)
|
||||
{
|
||||
enum cnh_result result;
|
||||
|
||||
result = CNH_RESULT_SUCCESS;
|
||||
|
||||
assert(irp);
|
||||
|
||||
if ((irp->op != CNH_IOHOOK_IRP_OP_OPEN && irp->fd != patch_hdd_check_fd) &&
|
||||
(irp->op != CNH_IOHOOK_IRP_OP_FDOPEN || irp->fdopen_fd != patch_hdd_check_fd)) {
|
||||
return cnh_iohook_invoke_next(irp);
|
||||
}
|
||||
|
||||
switch (irp->op) {
|
||||
case CNH_IOHOOK_IRP_OP_OPEN:
|
||||
{
|
||||
if (!strcmp(irp->open_filename, "/dev/hdd")) {
|
||||
patch_hdd_check_fd = cnh_iohook_open_dummy_fd();
|
||||
irp->fd = patch_hdd_check_fd;
|
||||
|
||||
log_debug("/dev/hdd opened: %d", patch_hdd_check_fd);
|
||||
} else {
|
||||
return cnh_iohook_invoke_next(irp);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_IOHOOK_IRP_OP_FDOPEN:
|
||||
{
|
||||
patch_hdd_check_file = cnh_filehook_open_dummy_file_handle();
|
||||
irp->fdopen_res = patch_hdd_check_file;
|
||||
|
||||
log_debug("/dev/hdd fdopen: %p", patch_hdd_check_file);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_IOHOOK_IRP_OP_READ:
|
||||
{
|
||||
uint32_t len;
|
||||
|
||||
log_debug("read, pos 0x%X, bytes %d", patch_hdd_check_boot_area_fp, irp->read.nbytes);
|
||||
|
||||
/* read up to eof, only */
|
||||
if (patch_hdd_check_boot_area_fp + irp->read.nbytes < patch_hdd_check_boot_area_size) {
|
||||
len = irp->read.nbytes;
|
||||
} else {
|
||||
len = patch_hdd_check_boot_area_size - patch_hdd_check_boot_area_fp;
|
||||
}
|
||||
|
||||
memcpy(irp->read.bytes, patch_hdd_check_boot_area + patch_hdd_check_boot_area_fp, len);
|
||||
|
||||
patch_hdd_check_boot_area_fp += len;
|
||||
irp->read.pos = len;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_IOHOOK_IRP_OP_SEEK:
|
||||
{
|
||||
log_debug("Seek: offset %d, orig %d", irp->seek_offset, irp->seek_origin);
|
||||
|
||||
switch (irp->seek_origin) {
|
||||
case SEEK_SET:
|
||||
patch_hdd_check_boot_area_fp = (size_t) irp->seek_offset;
|
||||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
patch_hdd_check_boot_area_fp += irp->seek_offset;
|
||||
break;
|
||||
|
||||
case SEEK_END:
|
||||
patch_hdd_check_boot_area_fp = patch_hdd_check_boot_area_size;
|
||||
patch_hdd_check_boot_area_fp += irp->seek_offset;
|
||||
break;
|
||||
|
||||
default:
|
||||
log_die("Unhandled origin %d", irp->seek_origin);
|
||||
break;
|
||||
}
|
||||
|
||||
irp->seek_pos = patch_hdd_check_boot_area_fp;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_IOHOOK_IRP_OP_IOCTL:
|
||||
{
|
||||
/* hdd information request */
|
||||
if (irp->ioctl_req == HDIO_GET_IDENTITY) {
|
||||
log_info("ioctl: fake ioctl for hdd (0x30D)");
|
||||
/* just return empty data instead of real data */
|
||||
struct hd_driveid* drid = (struct hd_driveid*) irp->ioctl.bytes;
|
||||
memset(drid, 0, sizeof(struct hd_driveid));
|
||||
irp->ioctl.pos = 0;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_IOHOOK_IRP_OP_CLOSE:
|
||||
patch_hdd_check_fd = -1;
|
||||
|
||||
log_debug("/dev/hdd closed");
|
||||
|
||||
/* Actually close dummy handle */
|
||||
return cnh_iohook_invoke_next(irp);
|
||||
|
||||
default:
|
||||
log_warn("Unhandled io op: %d", irp->op);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void patch_hdd_check_init(const uint8_t* boot_area_buffer, size_t len)
|
||||
{
|
||||
patch_hdd_check_boot_area = boot_area_buffer;
|
||||
patch_hdd_check_boot_area_size = len;
|
||||
|
||||
cnh_iohook_push_handler(patch_hdd_check_iohook);
|
||||
cnh_filehook_push_handler(patch_hdd_check_filehook);
|
||||
|
||||
log_info("Initialized: boot area size %d", patch_hdd_check_boot_area_size);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Patch the HDD check which (normally) ensures that the game is running on a
|
||||
* real HDD with valid data written to the boot area
|
||||
*/
|
||||
#ifndef PATCH_HDD_CHECK_H
|
||||
#define PATCH_HDD_CHECK_H
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*
|
||||
* @param boot_area_buffer Buffer with boot area sections of a real drive
|
||||
* @param len Length of buffer provided
|
||||
*/
|
||||
void patch_hdd_check_init(const uint8_t* boot_area_buffer, size_t len);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,36 @@
|
||||
#define LOG_MODULE "patch-hook-mon"
|
||||
|
||||
#include "capnhook/hooklib/filehook-mon.h"
|
||||
#include "capnhook/hooklib/fileopen-mon.h"
|
||||
#include "capnhook/hooklib/fshook-mon.h"
|
||||
#include "capnhook/hooklib/iohook-mon.h"
|
||||
#include "capnhook/hooklib/usbhook-mon.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
void patch_hook_mon_init(bool monitor_io, bool monitor_file, bool monitor_fs, bool monitor_usb, bool monitor_open)
|
||||
{
|
||||
if (monitor_open) {
|
||||
cnh_iohook_push_handler(cnh_fileopen_mon_iohook);
|
||||
cnh_filehook_push_handler(cnh_fileopen_mon_filehook);
|
||||
cnh_fshook_push_handler(cnh_fileopen_mon_fshook);
|
||||
}
|
||||
|
||||
if (monitor_io) {
|
||||
cnh_iohook_push_handler(cnh_iohook_mon);
|
||||
}
|
||||
|
||||
if (monitor_file) {
|
||||
cnh_filehook_push_handler(cnh_filehook_mon);
|
||||
}
|
||||
|
||||
if (monitor_fs) {
|
||||
cnh_fshook_push_handler(cnh_fshook_mon);
|
||||
}
|
||||
|
||||
if (monitor_usb) {
|
||||
cnh_usbhook_push_handler(cnh_usbhook_mon);
|
||||
}
|
||||
|
||||
log_info("Initialized");
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Patch module for monitoring various system calls. For development and debugging purpose.
|
||||
*/
|
||||
#ifndef PATCH_HOOK_MON_H
|
||||
#define PATCH_HOOK_MON_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* Initialize the module
|
||||
*
|
||||
* @param monitor_io True to enable monitoring of I/O calls
|
||||
* @param monitor_file True to enable monitoring of file related calls
|
||||
* @param monitor_fs True to enable monitoring of file system related calls
|
||||
* @param monitor_usb True to enable monitoring of usb calls
|
||||
* @param monitor_open True to enable monitoring of any file/device open calls
|
||||
*/
|
||||
void patch_hook_mon_init(bool monitor_io, bool monitor_file, bool monitor_fs, bool monitor_usb, bool monitor_open);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,235 @@
|
||||
#define LOG_MODULE "patch-main-loop"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <X11/Xutil.h>
|
||||
|
||||
#include "ptapi/io/x11-input-hook.h"
|
||||
|
||||
#include "capnhook/hook/lib.h"
|
||||
#include "capnhook/hook/sig.h"
|
||||
|
||||
#include "hook/patch/main-loop.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
typedef int (*XPending_t)(Display* display);
|
||||
typedef int (*XNextEvent_t)(Display* display, XEvent* event_return);
|
||||
|
||||
typedef const struct ptapi_io_x11_input_hook_handler* (*ptapi_io_x11_input_handler_hook_t)(void);
|
||||
|
||||
static XPending_t _patch_main_loop_real_XPending;
|
||||
static XNextEvent_t _patch_main_loop_real_XNextEvent;
|
||||
|
||||
static void _patch_main_loop_sigalarm_handler(int signal, __sighandler_t orig_handler);
|
||||
static bool _patch_main_loop_is_key_repeated(Display* display, XEvent* event);
|
||||
|
||||
static __sighandler_t _patch_main_loop_sigalarm_orig_handler;
|
||||
static bool _patch_main_loop_disable_built_in_inputs;
|
||||
static bool _patch_main_loop_disable_mk3_linux_ports_built_in_inputs;
|
||||
static pthread_mutex_t _patch_main_loop_input_handlers_lock;
|
||||
static const struct ptapi_io_x11_input_hook_handler** _patch_main_loop_input_handlers;
|
||||
static size_t _patch_main_loop_num_input_handlers;
|
||||
|
||||
void patch_main_loop_init(bool fix_sigalarm_main_loop, bool disable_built_in_inputs, bool disable_mk3_linux_port_inputs)
|
||||
{
|
||||
_patch_main_loop_disable_built_in_inputs = disable_built_in_inputs;
|
||||
_patch_main_loop_disable_mk3_linux_ports_built_in_inputs = disable_mk3_linux_port_inputs;
|
||||
|
||||
_patch_main_loop_sigalarm_orig_handler = NULL;
|
||||
|
||||
if (fix_sigalarm_main_loop) {
|
||||
log_info("Applying sigalarm handler fix");
|
||||
cnh_sig_install(SIGALRM, _patch_main_loop_sigalarm_handler);
|
||||
}
|
||||
|
||||
pthread_mutex_init(&_patch_main_loop_input_handlers_lock, NULL);
|
||||
|
||||
log_info("Initialized, disable built-in inputs %d", disable_built_in_inputs);
|
||||
}
|
||||
|
||||
void patch_main_loop_add_x11_input_handler(const char* lib_with_handler_impl)
|
||||
{
|
||||
const struct ptapi_io_x11_input_hook_handler** new_array;
|
||||
size_t new_size;
|
||||
void* lib_handle;
|
||||
ptapi_io_x11_input_handler_hook_t input_handler_hook_func;
|
||||
const struct ptapi_io_x11_input_hook_handler* input_handler_hook;
|
||||
|
||||
if (!lib_with_handler_impl) {
|
||||
log_die("No library path with input handler implementation specified");
|
||||
}
|
||||
|
||||
log_info("Loading library with x11-input-handler api implementation %s", lib_with_handler_impl);
|
||||
|
||||
lib_handle = cnh_lib_load(lib_with_handler_impl);
|
||||
|
||||
input_handler_hook_func = (ptapi_io_x11_input_handler_hook_t)
|
||||
cnh_lib_get_func_addr_handle(lib_handle, "ptapi_io_x11_input_handler_hook");
|
||||
|
||||
input_handler_hook = input_handler_hook_func();
|
||||
|
||||
if (!input_handler_hook) {
|
||||
log_warn("Library impl %s returned NULL hook handler, skipping", lib_with_handler_impl);
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&_patch_main_loop_input_handlers_lock);
|
||||
|
||||
new_size = _patch_main_loop_num_input_handlers + 1;
|
||||
new_array = realloc(_patch_main_loop_input_handlers, new_size * sizeof(struct ptapi_io_input_hook_handler*));
|
||||
|
||||
if (new_array != NULL) {
|
||||
_patch_main_loop_input_handlers = new_array;
|
||||
|
||||
_patch_main_loop_input_handlers[_patch_main_loop_num_input_handlers++] = input_handler_hook;
|
||||
} else {
|
||||
log_die("Out of memory");
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&_patch_main_loop_input_handlers_lock);
|
||||
|
||||
log_debug("Added input handler %p of lib %s", input_handler_hook, lib_with_handler_impl);
|
||||
}
|
||||
|
||||
int XPending(Display* display)
|
||||
{
|
||||
if (!_patch_main_loop_real_XPending) {
|
||||
_patch_main_loop_real_XPending = (XPending_t) cnh_lib_get_func_addr("XPending");
|
||||
}
|
||||
|
||||
/* poll the original SIGALRM handler to drive the pump engine */
|
||||
if (_patch_main_loop_sigalarm_orig_handler) {
|
||||
_patch_main_loop_sigalarm_orig_handler(SIGALRM);
|
||||
}
|
||||
|
||||
return _patch_main_loop_real_XPending(display);
|
||||
}
|
||||
|
||||
int XNextEvent(Display* display, XEvent* event_return)
|
||||
{
|
||||
int res;
|
||||
KeySym key;
|
||||
|
||||
if (!_patch_main_loop_real_XNextEvent) {
|
||||
_patch_main_loop_real_XNextEvent = (XNextEvent_t) cnh_lib_get_func_addr("XNextEvent");
|
||||
}
|
||||
|
||||
/* Trap KeyPress and KeyRelease events */
|
||||
res = _patch_main_loop_real_XNextEvent(display, event_return);
|
||||
|
||||
switch (event_return->type) {
|
||||
case KeyPress:
|
||||
{
|
||||
/* Mask out the modifier states X11 sets and read again */
|
||||
event_return->xkey.state &= ~ShiftMask;
|
||||
event_return->xkey.state &= ~LockMask;
|
||||
|
||||
XLookupString(&event_return->xkey, 0, 0, &key, 0);
|
||||
|
||||
/* Swallow some keys to disable them */
|
||||
if (_patch_main_loop_disable_built_in_inputs) {
|
||||
if (key == XK_F1 || key == XK_F2 || key == XK_F3) {
|
||||
event_return->type = GenericEvent;
|
||||
}
|
||||
}
|
||||
|
||||
/* Swallow all built-in inputs of the MK3 ports */
|
||||
if (_patch_main_loop_disable_mk3_linux_ports_built_in_inputs) {
|
||||
if (key == XK_g || key == XK_h || key == XK_j || key == XK_k || key == XK_l ||
|
||||
key == XK_q || key == XK_e || key == XK_s || key == XK_z || key == XK_c ||
|
||||
key == XK_KP_7 || key == XK_KP_9 || key == XK_KP_5 || key == XK_KP_1 || key == XK_KP_3) {
|
||||
event_return->type = GenericEvent;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dispatch to external input handlers */
|
||||
if (_patch_main_loop_num_input_handlers > 0) {
|
||||
pthread_mutex_lock(&_patch_main_loop_input_handlers_lock);
|
||||
|
||||
for (size_t i = 0; i < _patch_main_loop_num_input_handlers; i++) {
|
||||
if (_patch_main_loop_input_handlers[i]->dispatch_key_press) {
|
||||
_patch_main_loop_input_handlers[i]->dispatch_key_press(key);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&_patch_main_loop_input_handlers_lock);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case KeyRelease:
|
||||
{
|
||||
/* Mask out the modifier states X11 sets and read again */
|
||||
event_return->xkey.state &= ~ShiftMask;
|
||||
event_return->xkey.state &= ~LockMask;
|
||||
|
||||
XLookupString(&event_return->xkey, 0, 0, &key, 0);
|
||||
|
||||
/* Swallow some keys to disable them */
|
||||
if (_patch_main_loop_disable_built_in_inputs) {
|
||||
if (key == XK_F1 || key == XK_F2 || key == XK_F3) {
|
||||
event_return->type = GenericEvent;
|
||||
}
|
||||
}
|
||||
|
||||
/* Swallow all built-in inputs of the MK3 ports */
|
||||
if (_patch_main_loop_disable_mk3_linux_ports_built_in_inputs) {
|
||||
if (key == XK_g || key == XK_h || key == XK_j || key == XK_k || key == XK_l ||
|
||||
key == XK_q || key == XK_e || key == XK_s || key == XK_z || key == XK_c ||
|
||||
key == XK_KP_7 || key == XK_KP_9 || key == XK_KP_5 || key == XK_KP_1 || key == XK_KP_3) {
|
||||
event_return->type = GenericEvent;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dispatch to external input handlers */
|
||||
if (_patch_main_loop_num_input_handlers > 0) {
|
||||
/* Dispatch release if key is not repeated */
|
||||
if (!_patch_main_loop_is_key_repeated(display, event_return)) {
|
||||
pthread_mutex_lock(&_patch_main_loop_input_handlers_lock);
|
||||
|
||||
for (size_t i = 0; i < _patch_main_loop_num_input_handlers; i++) {
|
||||
if (_patch_main_loop_input_handlers[i]->dispatch_key_release) {
|
||||
_patch_main_loop_input_handlers[i]->dispatch_key_release(key);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&_patch_main_loop_input_handlers_lock);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void _patch_main_loop_sigalarm_handler(int signal, __sighandler_t orig_handler)
|
||||
{
|
||||
/* do nothing on sigalrm */
|
||||
_patch_main_loop_sigalarm_orig_handler = orig_handler;
|
||||
}
|
||||
|
||||
static bool _patch_main_loop_is_key_repeated(Display* display, XEvent* event)
|
||||
{
|
||||
/* When a key is repeated, there will be two events: released, followed by another immediate pressed.
|
||||
So check to see if another pressed is present */
|
||||
if (!_patch_main_loop_real_XPending(display)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XEvent e;
|
||||
XPeekEvent(display, &e);
|
||||
|
||||
if (e.type == KeyPress && e.xkey.keycode == event->xkey.keycode && (e.xkey.time - event->xkey.time) < 2) {
|
||||
_patch_main_loop_real_XNextEvent(display, &e);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* This module fixes different things on the main loop of the game.
|
||||
*
|
||||
* First, it hooks into the X11 event loop to grab and dispatch inputs for
|
||||
* further IO hooks or input emulation.
|
||||
*
|
||||
* The second thing is quite stupid and fixes crashing on newer kernels
|
||||
* because AM used sigalarm to drive certain parts of their engine.
|
||||
* This only applies to one generation of the game engine which includes the
|
||||
* releases: Exceed, Exceed2, Zero and NX
|
||||
*
|
||||
* Detailed analysis:
|
||||
*
|
||||
* AM decided to poll the CPIU::Update function which contains updating
|
||||
* the render state as well as decoding sound data, using the signal SIGALRM.
|
||||
* This signal is triggered periodically and drives the whole engine.
|
||||
* But, starting kernel versions newer than 2.4.27 something changed which
|
||||
* causes the game to deadlock an _ALL_ kernels newer than said version. Trying
|
||||
* to fix the deadlock issue makes things even worse and only introduces
|
||||
* segfaults with random crashes. So this is not the right approach.
|
||||
* Starting NX2 AM used a newer kernel and apparently got the same problem
|
||||
* with their OS. They fixed it by moving music_send_data from CPIU::Update()
|
||||
* to the IO thread. Splitting CPIU::Update that way, this seems to work fine.
|
||||
* However, trying this with the old games lacking the patch, we are getting
|
||||
* segfaults (again).
|
||||
* It looks like there is an issue with firing and/or handling of SIGALRM in
|
||||
* another thread than the main thread (don't ask me why). This leads to the
|
||||
* solution of the problem: moving the call of CPIU::Update()to the main
|
||||
* thread which is only pumping XEvents (i.e. nothing).
|
||||
* So let's give the main thread some work: Detouring XPending and calling
|
||||
* the registered signal handler for SIGALRM of the game in there and muting
|
||||
* signal handling for SIGALRM. This solves all deadlocking and segfault
|
||||
* issues which are related to audio playback in combination with decoding a
|
||||
* video.
|
||||
*/
|
||||
#ifndef PATCH_MAIN_LOOP_H
|
||||
#define PATCH_MAIN_LOOP_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <X11/Xutil.h>
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*
|
||||
* @param fix_sigalarm_main_loop True to enable the sigalarm thread fix (doc see above)
|
||||
* @param disable_built_in_inputs Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)
|
||||
* @param disable_mk3_linux_port_inputs Disable the built-in keyboard inputs on the MK3 linux ports
|
||||
*/
|
||||
void patch_main_loop_init(bool fix_sigalarm_main_loop, bool disable_built_in_inputs,
|
||||
bool disable_mk3_linux_port_inputs);
|
||||
|
||||
/**
|
||||
* Register a handler (multiple possible) to receive X11 input events, e.g. key pressed, released
|
||||
*
|
||||
* @param lib_with_handler_impl Path to a library implementing the x11-input-handler API
|
||||
*/
|
||||
void patch_main_loop_add_x11_input_handler(const char* lib_with_handler_impl);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,72 @@
|
||||
#define LOG_MODULE "patch-microdog34"
|
||||
|
||||
#define USBDOG_ENDPOINT "/dev/usbdog"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "capnhook/hook/iohook.h"
|
||||
|
||||
#include "sec/microdog34/microdog34.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static int _patch_microdog34_fd = -1;
|
||||
|
||||
enum cnh_result patch_microdog34_iohook(struct cnh_iohook_irp *irp);
|
||||
|
||||
void patch_microdog34_init(const uint8_t* key_data, size_t len)
|
||||
{
|
||||
sec_microdog34_init(key_data, len);
|
||||
|
||||
cnh_iohook_push_handler(patch_microdog34_iohook);
|
||||
log_info("Initialized");
|
||||
}
|
||||
|
||||
enum cnh_result patch_microdog34_iohook(struct cnh_iohook_irp *irp)
|
||||
{
|
||||
if (irp->op != CNH_IOHOOK_IRP_OP_OPEN && irp->fd != _patch_microdog34_fd) {
|
||||
return cnh_iohook_invoke_next(irp);
|
||||
}
|
||||
|
||||
switch (irp->op) {
|
||||
case CNH_IOHOOK_IRP_OP_OPEN:
|
||||
{
|
||||
if (!strcmp(irp->open_filename, USBDOG_ENDPOINT)) {
|
||||
_patch_microdog34_fd = cnh_iohook_open_dummy_fd();
|
||||
irp->fd = _patch_microdog34_fd;
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
return cnh_iohook_invoke_next(irp);
|
||||
}
|
||||
|
||||
case CNH_IOHOOK_IRP_OP_CLOSE:
|
||||
{
|
||||
assert(irp->fd);
|
||||
|
||||
cnh_iohook_close_dummy_fd(irp->fd);
|
||||
_patch_microdog34_fd = -1;
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
case CNH_IOHOOK_IRP_OP_READ:
|
||||
{
|
||||
/* Just stub and return success */
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
case CNH_IOHOOK_IRP_OP_IOCTL:
|
||||
{
|
||||
irp->ioctl.pos = (size_t) sec_microdog34_process(irp->ioctl_req, irp->ioctl.bytes);
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
default:
|
||||
log_die("Unhandled operation: %d", irp->op);
|
||||
return CNH_RESULT_OTHER_ERROR;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Patch module to install the dongle emulator for a microdog 3.4
|
||||
*/
|
||||
#ifndef PATCH_MICRODOG34_H
|
||||
#define PATCH_MICRODOG34_H
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*
|
||||
* @param key_data Pointer to loaded key data (dog attributes, key table)
|
||||
* @param len Length of the buffer
|
||||
*/
|
||||
void patch_microdog34_init(const uint8_t* key_data, size_t len);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
#define LOG_MODULE "patch-microdog40"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "sec/microdog40/microdog40d.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static pthread_t patch_microdog40_thread;
|
||||
|
||||
static void* patch_microdog40_thread_run(void* args)
|
||||
{
|
||||
log_info("Running daemon thread");
|
||||
|
||||
sec_microdog40d_run();
|
||||
|
||||
log_info("Finished daemon thread");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void patch_microdog40_init(const uint8_t* key_data, size_t len)
|
||||
{
|
||||
sec_microdog40d_init(key_data, len);
|
||||
|
||||
/* Run the daemon in another thread */
|
||||
pthread_create(&patch_microdog40_thread, NULL,
|
||||
patch_microdog40_thread_run, NULL);
|
||||
|
||||
log_info("Initialized");
|
||||
}
|
||||
|
||||
void patch_microdog40_shutdown(void)
|
||||
{
|
||||
log_info("Shutting down");
|
||||
|
||||
sec_microdog40d_shutdown();
|
||||
|
||||
pthread_join(patch_microdog40_thread, NULL);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Patch module to install the dongle emulator for a microdog 4.0
|
||||
*/
|
||||
#ifndef PATCH_MICRODOG34_H
|
||||
#define PATCH_MICRODOG34_H
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*
|
||||
* @param key_data Pointer to loaded key data (dog attributes, key table)
|
||||
* @param len Length of the buffer
|
||||
*/
|
||||
void patch_microdog40_init(const uint8_t* key_data, size_t len);
|
||||
|
||||
/**
|
||||
* Shutdown and remove the emulator
|
||||
*/
|
||||
void patch_microdog40_shutdown(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,105 @@
|
||||
#define LOG_MODULE "patch-mounts"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "capnhook/hook/filehook.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
/* looks odd but hddd is intentional */
|
||||
static const char* patch_mount_proc_mount_file = "/dev/hddd /mnt/hd";
|
||||
|
||||
static FILE* patch_mount_file = NULL;
|
||||
static bool patch_mount_eof;
|
||||
|
||||
static enum cnh_result patch_hdd_check_filehook(struct cnh_filehook_irp *irp)
|
||||
{
|
||||
enum cnh_result result;
|
||||
|
||||
result = CNH_RESULT_SUCCESS;
|
||||
|
||||
assert(irp);
|
||||
|
||||
if (irp->op != CNH_FILEHOOK_IRP_OP_OPEN && irp->file != patch_mount_file) {
|
||||
return cnh_filehook_invoke_next(irp);
|
||||
}
|
||||
|
||||
switch (irp->op) {
|
||||
case CNH_FILEHOOK_IRP_OP_OPEN:
|
||||
{
|
||||
if (!strcmp(irp->open_filename, "/proc/mounts")) {
|
||||
patch_mount_file = cnh_filehook_open_dummy_file_handle();
|
||||
irp->file = patch_mount_file;
|
||||
|
||||
patch_mount_eof = false;
|
||||
|
||||
log_debug("/proc/mounts opened: %p", irp->file);
|
||||
} else {
|
||||
return cnh_filehook_invoke_next(irp);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_FGETS:
|
||||
{
|
||||
size_t size;
|
||||
|
||||
log_info("/proc/mounts fake read");
|
||||
|
||||
size = strlen(patch_mount_proc_mount_file) + 1;
|
||||
|
||||
/* +1: copy the null terminator, too */
|
||||
memcpy(&irp->read.bytes[irp->read.pos], patch_mount_proc_mount_file, size);
|
||||
irp->read.pos += size;
|
||||
|
||||
patch_mount_eof = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_SEEK:
|
||||
{
|
||||
/* Ignore seeking */
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_TELL:
|
||||
{
|
||||
/* Return size */
|
||||
irp->tell_offset = strlen(patch_mount_proc_mount_file) + 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_EOF:
|
||||
{
|
||||
irp->eof = patch_mount_eof;
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_CLOSE:
|
||||
{
|
||||
patch_mount_file = NULL;
|
||||
|
||||
/* Actually close dummy handle */
|
||||
return cnh_filehook_invoke_next(irp);
|
||||
}
|
||||
|
||||
default:
|
||||
log_warn("Unhandled file op: %d", irp->op);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void patch_mounts_init(void)
|
||||
{
|
||||
cnh_filehook_push_handler(patch_hdd_check_filehook);
|
||||
log_info("Initialized");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Starting NX2, the game is checking /proc/mounts for the entry
|
||||
* "/dev/hddd /mnt/hd" -> hook and inject that
|
||||
*/
|
||||
#ifndef PATCH_MOUNTS_H
|
||||
#define PATCH_MOUNTS_H
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*/
|
||||
void patch_mounts_init(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,594 @@
|
||||
#define LOG_MODULE "patch-net-profile"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "asset/nx2/lib/usb-rank.h"
|
||||
#include "asset/nx2/lib/usb-save.h"
|
||||
|
||||
#include "capnhook/hook/filehook.h"
|
||||
|
||||
#include "pumpnet/lib/profile-token.h"
|
||||
#include "pumpnet/lib/pumpnet.h"
|
||||
|
||||
#include "util/fs.h"
|
||||
#include "util/log.h"
|
||||
#include "util/time.h"
|
||||
|
||||
#define PUMPNET_MAX_NUM_PLAYERS 2
|
||||
#define PUMPNET_PROFILE_FILE_NAME "pumpnet.bin"
|
||||
#define PUMPNET_TRY_CONNECT_TIMEOUT_MS 2000
|
||||
|
||||
struct profile_virtual_mnt_point_file_info {
|
||||
int player;
|
||||
enum pumpnet_lib_file_type file_type;
|
||||
const char* file_path;
|
||||
size_t file_size;
|
||||
};
|
||||
|
||||
struct profile_virtual_mnt_point_info {
|
||||
struct {
|
||||
struct profile_virtual_mnt_point_file_info file_info[PUMPNET_LIB_FILE_TYPE_COUNT];
|
||||
} player[PUMPNET_MAX_NUM_PLAYERS];
|
||||
};
|
||||
|
||||
struct profile_virtual_file {
|
||||
uint64_t player_ref_id;
|
||||
FILE* handle;
|
||||
const struct profile_virtual_mnt_point_file_info* file_info;
|
||||
size_t buffer_pos;
|
||||
uint8_t* buffer;
|
||||
bool is_written;
|
||||
};
|
||||
|
||||
struct profile_virtual_mnt_point {
|
||||
struct profile_virtual_file files[PUMPNET_LIB_FILE_TYPE_COUNT];
|
||||
};
|
||||
|
||||
static enum cnh_result _patch_net_profile_filehook(struct cnh_filehook_irp* irp);
|
||||
static enum cnh_result _patch_net_profile_fopen(struct cnh_filehook_irp* irp);
|
||||
static enum cnh_result _patch_net_profile_fclose(struct cnh_filehook_irp* irp,
|
||||
struct profile_virtual_file* virtual_file);
|
||||
static enum cnh_result _patch_net_profile_fread(struct cnh_filehook_irp* irp,
|
||||
struct profile_virtual_file* virtual_file);
|
||||
static enum cnh_result _patch_net_profile_fwrite(struct cnh_filehook_irp* irp,
|
||||
struct profile_virtual_file* virtual_file);
|
||||
static enum cnh_result _patch_net_profile_fseek(struct cnh_filehook_irp* irp,
|
||||
struct profile_virtual_file* virtual_file);
|
||||
static enum cnh_result _patch_net_profile_ftell(struct cnh_filehook_irp* irp,
|
||||
struct profile_virtual_file* virtual_file);
|
||||
static enum cnh_result _patch_net_profile_feof(struct cnh_filehook_irp* irp,
|
||||
struct profile_virtual_file* virtual_file);
|
||||
|
||||
static struct profile_virtual_file* _patch_net_profile_get_virtual_mnt_point(FILE* handle);
|
||||
static bool _patch_net_profile_determine_is_profile_and_player_and_file_type(
|
||||
struct cnh_filehook_irp* irp,
|
||||
uint8_t* player,
|
||||
enum pumpnet_lib_file_type* file_type);
|
||||
static enum cnh_result _patch_net_profile_open_pumpnet_profile_file(
|
||||
struct cnh_filehook_irp* irp,
|
||||
uint8_t player,
|
||||
enum pumpnet_lib_file_type file_type);
|
||||
static bool _patch_net_profile_close_pumpnet_profile_file(struct profile_virtual_file* virtual_file);
|
||||
|
||||
static const struct profile_virtual_mnt_point_info _patch_net_profile_virtual_mnt_point_infos[] = {
|
||||
{
|
||||
.player = {
|
||||
{
|
||||
.file_info = {
|
||||
{
|
||||
.player = 0,
|
||||
.file_type = PUMPNET_LIB_FILE_TYPE_SAVE,
|
||||
.file_path = "/mnt/0/nx2save.bin",
|
||||
.file_size = ASSET_NX2_USB_SAVE_SIZE
|
||||
},
|
||||
{
|
||||
.player = 0,
|
||||
.file_type = PUMPNET_LIB_FILE_TYPE_RANK,
|
||||
.file_path = "/mnt/0/nx2rank.bin",
|
||||
.file_size = ASSET_NX2_USB_RANK_SIZE
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
.file_info = {
|
||||
{
|
||||
.player = 1,
|
||||
.file_type = PUMPNET_LIB_FILE_TYPE_SAVE,
|
||||
.file_path = "/mnt/1/nx2save.bin",
|
||||
.file_size = ASSET_NX2_USB_SAVE_SIZE
|
||||
},
|
||||
{
|
||||
.player = 1,
|
||||
.file_type = PUMPNET_LIB_FILE_TYPE_RANK,
|
||||
.file_path = "/mnt/1/nx2rank.bin",
|
||||
.file_size = ASSET_NX2_USB_RANK_SIZE
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static const struct profile_virtual_mnt_point_info* _patch_net_profile_file_info_ref;
|
||||
static struct profile_virtual_mnt_point _patch_net_profile_virtual_mnt_points[PUMPNET_MAX_NUM_PLAYERS];
|
||||
static pthread_mutex_t _patch_net_profile_mutex;
|
||||
|
||||
static enum cnh_result _patch_net_profile_filehook(struct cnh_filehook_irp* irp)
|
||||
{
|
||||
if (irp->op == CNH_FILEHOOK_IRP_OP_OPEN) {
|
||||
return _patch_net_profile_fopen(irp);
|
||||
} else {
|
||||
struct profile_virtual_file* virt_file = _patch_net_profile_get_virtual_mnt_point(irp->file);
|
||||
|
||||
if (virt_file != NULL) {
|
||||
switch (irp->op) {
|
||||
case CNH_FILEHOOK_IRP_OP_CLOSE:
|
||||
return _patch_net_profile_fclose(irp, virt_file);
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_READ:
|
||||
return _patch_net_profile_fread(irp, virt_file);
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_WRITE:
|
||||
return _patch_net_profile_fwrite(irp, virt_file);
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_FGETS:
|
||||
log_die("Unsupported operation");
|
||||
return CNH_RESULT_OTHER_ERROR;
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_SEEK:
|
||||
return _patch_net_profile_fseek(irp, virt_file);
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_TELL:
|
||||
return _patch_net_profile_ftell(irp, virt_file);
|
||||
|
||||
case CNH_FILEHOOK_IRP_OP_EOF:
|
||||
return _patch_net_profile_feof(irp, virt_file);
|
||||
|
||||
default:
|
||||
log_die("Unhandled switch case");
|
||||
return CNH_RESULT_OTHER_ERROR;
|
||||
}
|
||||
} else {
|
||||
return cnh_filehook_invoke_next(irp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_net_profile_fopen(struct cnh_filehook_irp* irp)
|
||||
{
|
||||
uint8_t player;
|
||||
enum pumpnet_lib_file_type file_type;
|
||||
player = -1;
|
||||
file_type = PUMPNET_LIB_FILE_TYPE_COUNT;
|
||||
|
||||
// not a profile file, pass
|
||||
if (!_patch_net_profile_determine_is_profile_and_player_and_file_type(irp, &player, &file_type)) {
|
||||
return cnh_filehook_invoke_next(irp);
|
||||
}
|
||||
|
||||
return _patch_net_profile_open_pumpnet_profile_file(irp, player, file_type);
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_net_profile_fclose(struct cnh_filehook_irp* irp,
|
||||
struct profile_virtual_file* virtual_file)
|
||||
{
|
||||
log_debug("fclose %s", virtual_file->file_info->file_path);
|
||||
|
||||
return _patch_net_profile_close_pumpnet_profile_file(virtual_file) ? CNH_RESULT_SUCCESS : CNH_RESULT_OTHER_ERROR;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_net_profile_fread(struct cnh_filehook_irp* irp,
|
||||
struct profile_virtual_file* virtual_file)
|
||||
{
|
||||
uint32_t len;
|
||||
|
||||
pthread_mutex_lock(&_patch_net_profile_mutex);
|
||||
|
||||
log_debug("fread %s, pos 0x%X, bytes %d", virtual_file->file_info->file_path, virtual_file->buffer_pos,
|
||||
irp->read.nbytes);
|
||||
|
||||
/* read up to eof, only */
|
||||
if (virtual_file->buffer_pos + irp->read.nbytes < virtual_file->file_info->file_size) {
|
||||
len = irp->read.nbytes;
|
||||
} else {
|
||||
len = virtual_file->file_info->file_size - virtual_file->buffer_pos;
|
||||
}
|
||||
|
||||
memcpy(&irp->read.bytes[irp->read.pos], virtual_file->buffer + virtual_file->buffer_pos, len);
|
||||
|
||||
virtual_file->buffer_pos += len;
|
||||
irp->read.pos += len;
|
||||
|
||||
pthread_mutex_unlock(&_patch_net_profile_mutex);
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_net_profile_fwrite(struct cnh_filehook_irp* irp,
|
||||
struct profile_virtual_file* virtual_file)
|
||||
{
|
||||
uint32_t len;
|
||||
|
||||
pthread_mutex_lock(&_patch_net_profile_mutex);
|
||||
|
||||
log_debug("fwrite %s, pos 0x%X, bytes %d", virtual_file->file_info->file_path, virtual_file->buffer_pos,
|
||||
irp->read.nbytes);
|
||||
|
||||
/* write up to eof, only */
|
||||
if (virtual_file->buffer_pos + irp->write.nbytes < virtual_file->file_info->file_size) {
|
||||
len = irp->write.nbytes;
|
||||
} else {
|
||||
len = virtual_file->file_info->file_size - virtual_file->buffer_pos;
|
||||
}
|
||||
|
||||
memcpy(virtual_file->buffer + virtual_file->buffer_pos, irp->write.bytes, len);
|
||||
|
||||
virtual_file->buffer_pos += len;
|
||||
virtual_file->is_written = true;
|
||||
irp->write.pos += len;
|
||||
|
||||
pthread_mutex_unlock(&_patch_net_profile_mutex);
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_net_profile_fseek(struct cnh_filehook_irp* irp,
|
||||
struct profile_virtual_file* virtual_file)
|
||||
{
|
||||
pthread_mutex_lock(&_patch_net_profile_mutex);
|
||||
|
||||
switch (irp->seek_origin) {
|
||||
case SEEK_SET:
|
||||
virtual_file->buffer_pos = (size_t) irp->seek_offset;
|
||||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
virtual_file->buffer_pos += (size_t) irp->seek_offset;
|
||||
break;
|
||||
|
||||
case SEEK_END:
|
||||
virtual_file->buffer_pos = virtual_file->file_info->file_size;
|
||||
virtual_file->buffer_pos += (size_t) irp->seek_offset;
|
||||
break;
|
||||
|
||||
default:
|
||||
log_die("Unhandled origin %d", irp->seek_origin);
|
||||
break;
|
||||
}
|
||||
|
||||
log_debug("fseek %s, offset 0x%X, origin %d", virtual_file->file_info->file_path, irp->seek_offset,
|
||||
irp->seek_origin);
|
||||
|
||||
pthread_mutex_unlock(&_patch_net_profile_mutex);
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_net_profile_ftell(struct cnh_filehook_irp* irp,
|
||||
struct profile_virtual_file* virtual_file)
|
||||
{
|
||||
pthread_mutex_lock(&_patch_net_profile_mutex);
|
||||
|
||||
irp->tell_offset = virtual_file->buffer_pos;
|
||||
|
||||
pthread_mutex_unlock(&_patch_net_profile_mutex);
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_net_profile_feof(struct cnh_filehook_irp* irp,
|
||||
struct profile_virtual_file* virtual_file)
|
||||
{
|
||||
pthread_mutex_lock(&_patch_net_profile_mutex);
|
||||
|
||||
irp->eof = virtual_file->buffer_pos == virtual_file->file_info->file_size;
|
||||
|
||||
pthread_mutex_unlock(&_patch_net_profile_mutex);
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// =====================================================================================================================
|
||||
|
||||
static struct profile_virtual_file* _patch_net_profile_get_virtual_mnt_point(FILE* handle)
|
||||
{
|
||||
for (int i = 0; i < PUMPNET_MAX_NUM_PLAYERS; i++) {
|
||||
for (int j = 0; j < PUMPNET_LIB_FILE_TYPE_COUNT; j++) {
|
||||
if (_patch_net_profile_virtual_mnt_points->files[j].handle == handle) {
|
||||
return &_patch_net_profile_virtual_mnt_points->files[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool _patch_net_profile_determine_is_profile_and_player_and_file_type(
|
||||
struct cnh_filehook_irp* irp,
|
||||
uint8_t* player,
|
||||
enum pumpnet_lib_file_type* file_type)
|
||||
{
|
||||
log_assert(irp);
|
||||
log_assert(player);
|
||||
log_assert(file_type);
|
||||
|
||||
// compare to static string to avoid slowing everything down
|
||||
for (int i = 0; i < PUMPNET_MAX_NUM_PLAYERS; i++) {
|
||||
for (int j = 0; j < PUMPNET_LIB_FILE_TYPE_COUNT; j++) {
|
||||
if (!strcmp(irp->open_filename,
|
||||
_patch_net_profile_virtual_mnt_point_infos->player[i].file_info[j].file_path)) {
|
||||
*player = i;
|
||||
*file_type = (enum pumpnet_lib_file_type) j;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct profile_virtual_file* _patch_net_profile_setup_virtual_file(
|
||||
uint8_t player,
|
||||
enum pumpnet_lib_file_type file_type,
|
||||
uint64_t player_ref_id)
|
||||
{
|
||||
struct profile_virtual_file* virtual_file;
|
||||
|
||||
log_debug("Setup virtual file, player %d, file_type %d", player, file_type);
|
||||
|
||||
virtual_file = &_patch_net_profile_virtual_mnt_points[player].files[file_type];
|
||||
|
||||
// sanity checks
|
||||
log_assert(!virtual_file->handle);
|
||||
log_assert(!virtual_file->file_info);
|
||||
log_assert(!virtual_file->buffer);
|
||||
|
||||
virtual_file->player_ref_id = player_ref_id;
|
||||
virtual_file->handle = cnh_filehook_open_dummy_file_handle();
|
||||
virtual_file->file_info = &_patch_net_profile_virtual_mnt_point_infos->player[player].file_info[file_type];
|
||||
virtual_file->buffer_pos = 0;
|
||||
virtual_file->is_written = false;
|
||||
|
||||
virtual_file->buffer = (uint8_t*) malloc(virtual_file->file_info->file_size);
|
||||
|
||||
log_debug("Setup virtual file done, size %d", virtual_file->file_info->file_size);
|
||||
|
||||
return virtual_file;
|
||||
}
|
||||
|
||||
static void _patch_net_profile_destroy_virtual_file(uint8_t player, enum pumpnet_lib_file_type file_type)
|
||||
{
|
||||
struct profile_virtual_file* virtual_file;
|
||||
|
||||
log_debug("Destroying virtual file, player %d, file_type %d", player, file_type);
|
||||
|
||||
virtual_file = &_patch_net_profile_virtual_mnt_points[player].files[file_type];
|
||||
|
||||
// sanity checks
|
||||
log_assert(virtual_file->handle);
|
||||
log_assert(virtual_file->file_info);
|
||||
log_assert(virtual_file->buffer);
|
||||
|
||||
cnh_filehook_close_dummy_file_handle(virtual_file->handle);
|
||||
virtual_file->handle = NULL;
|
||||
virtual_file->file_info = NULL;
|
||||
free(virtual_file->buffer);
|
||||
virtual_file->buffer = NULL;
|
||||
}
|
||||
|
||||
static bool _patch_net_profile_get_token(int player, uint64_t* player_ref_id)
|
||||
{
|
||||
log_assert(player_ref_id);
|
||||
log_assert(player >= 0);
|
||||
log_assert(player <= 1);
|
||||
|
||||
char path_profile_token[128];
|
||||
|
||||
*player_ref_id = 0;
|
||||
|
||||
sprintf(path_profile_token, "/mnt/%d/%s", player, PUMPNET_PROFILE_FILE_NAME);
|
||||
|
||||
if (util_fs_path_exists(path_profile_token)) {
|
||||
log_info("Found pumpnet profile file %s", path_profile_token);
|
||||
|
||||
if (pumpnet_lib_profile_token_load(path_profile_token, player_ref_id)) {
|
||||
return true;
|
||||
} else {
|
||||
log_error("Loading pumpnet profile token %s failed, fallback to local profiles", path_profile_token);
|
||||
}
|
||||
} else {
|
||||
log_info("No pumpnet profile found, fallback to local profile");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _patch_net_profile_download_profile_file(struct profile_virtual_file* virtual_file)
|
||||
{
|
||||
int player;
|
||||
enum pumpnet_lib_file_type file_type;
|
||||
uint64_t player_ref_id;
|
||||
|
||||
player = virtual_file->file_info->player;
|
||||
file_type = virtual_file->file_info->file_type;
|
||||
player_ref_id = virtual_file->player_ref_id;
|
||||
|
||||
log_info("Profile file player %d, file_type %d, refId %llX downloading from server...",
|
||||
player,
|
||||
file_type,
|
||||
player_ref_id);
|
||||
|
||||
if (!pumpnet_lib_get(file_type, player_ref_id, virtual_file->buffer, virtual_file->file_info->file_size)) {
|
||||
log_error("Downloading file player %d, file_type %d, refId %llX failed",
|
||||
player,
|
||||
file_type,
|
||||
player_ref_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
log_info("Downloading file player %d, file_type %d, refId %llX successful",
|
||||
player,
|
||||
file_type,
|
||||
player_ref_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_net_profile_open_pumpnet_profile_file(
|
||||
struct cnh_filehook_irp* irp,
|
||||
uint8_t player,
|
||||
enum pumpnet_lib_file_type file_type)
|
||||
{
|
||||
uint64_t player_ref_id;
|
||||
|
||||
log_debug("open %s %d %d", irp->open_filename, player, file_type);
|
||||
|
||||
// check if pumpnet.bin token is available and server reachable
|
||||
// download all profile files on the first file request and hook all following files to virtual endpoints of
|
||||
// the already downloaded and buffered data
|
||||
if (_patch_net_profile_get_token(player, &player_ref_id)) {
|
||||
struct profile_virtual_file* virtual_file =
|
||||
_patch_net_profile_setup_virtual_file(player, file_type, player_ref_id);
|
||||
|
||||
if (_patch_net_profile_download_profile_file(virtual_file)) {
|
||||
irp->file = virtual_file->handle;
|
||||
pthread_mutex_unlock(&_patch_net_profile_mutex);
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
} else {
|
||||
_patch_net_profile_destroy_virtual_file(player, file_type);
|
||||
|
||||
pthread_mutex_unlock(&_patch_net_profile_mutex);
|
||||
|
||||
return CNH_RESULT_NO_SUCH_FILE_OR_DIR;
|
||||
}
|
||||
}
|
||||
|
||||
return cnh_filehook_invoke_next(irp);
|
||||
}
|
||||
|
||||
static bool _patch_net_profile_upload_profile_file(struct profile_virtual_file* virtual_file)
|
||||
{
|
||||
int player;
|
||||
enum pumpnet_lib_file_type file_type;
|
||||
uint64_t player_ref_id;
|
||||
|
||||
player = virtual_file->file_info->player;
|
||||
file_type = virtual_file->file_info->file_type;
|
||||
player_ref_id = virtual_file->player_ref_id;
|
||||
|
||||
log_info("Profile file player %d, file_type %d, refId %llX uploading to server...",
|
||||
player,
|
||||
file_type,
|
||||
player_ref_id);
|
||||
|
||||
if (!pumpnet_lib_put(file_type, player_ref_id, virtual_file->buffer, virtual_file->file_info->file_size)) {
|
||||
log_error("Uploading file player %d, file_type %d, refId %llX failed",
|
||||
player,
|
||||
file_type,
|
||||
player_ref_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
log_info("Uploading file player %d, file_type %d, refId %llX successful",
|
||||
player,
|
||||
file_type,
|
||||
player_ref_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _patch_net_profile_close_pumpnet_profile_file(struct profile_virtual_file* virtual_file)
|
||||
{
|
||||
bool res = true;
|
||||
|
||||
pthread_mutex_lock(&_patch_net_profile_mutex);
|
||||
|
||||
if (virtual_file->is_written) {
|
||||
res = _patch_net_profile_upload_profile_file(virtual_file);
|
||||
}
|
||||
|
||||
_patch_net_profile_destroy_virtual_file(virtual_file->file_info->player, virtual_file->file_info->file_type);
|
||||
|
||||
pthread_mutex_unlock(&_patch_net_profile_mutex);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// =====================================================================================================================
|
||||
|
||||
void patch_net_profile_init(
|
||||
enum asset_game_version game,
|
||||
const char* pumpnet_server_addr,
|
||||
uint64_t machine_id,
|
||||
const char* cert_dir_path,
|
||||
bool verbose_debug_log)
|
||||
{
|
||||
uint8_t idx;
|
||||
|
||||
/* use a switch instead of offset'ing the enum for additional safety and block unsupported games */
|
||||
switch (game) {
|
||||
case ASSET_GAME_VERSION_NX2:
|
||||
idx = 0;
|
||||
break;
|
||||
|
||||
// case PUMPNET_LIB_GAME_NXA:
|
||||
// idx = 1;
|
||||
// break;
|
||||
//
|
||||
// case PUMPNET_LIB_GAME_FST:
|
||||
// idx = 2;
|
||||
// break;
|
||||
//
|
||||
// case PUMPNET_LIB_GAME_FEX:
|
||||
// idx = 3;
|
||||
// break;
|
||||
//
|
||||
// case PUMPNET_LIB_GAME_F2:
|
||||
// idx = 4;
|
||||
// break;
|
||||
case ASSET_GAME_VERSION_NXA:
|
||||
case ASSET_GAME_VERSION_FIESTA:
|
||||
case ASSET_GAME_VERSION_FIESTA_EX:
|
||||
case ASSET_GAME_VERSION_FIESTA_2:
|
||||
|
||||
default:
|
||||
log_error("Unsupported game version %d specified, disabled network service", game);
|
||||
return;
|
||||
}
|
||||
|
||||
_patch_net_profile_file_info_ref = &_patch_net_profile_virtual_mnt_point_infos[idx];
|
||||
|
||||
size_t max_data_size = 0;
|
||||
|
||||
// determine max file size for network buffers, file sizes identical on for all players.
|
||||
for (int i = 0; i < PUMPNET_LIB_FILE_TYPE_COUNT; i++) {
|
||||
max_data_size += _patch_net_profile_file_info_ref->player[0].file_info[i].file_size;
|
||||
}
|
||||
|
||||
// set buffers to NULL to detect illegal states
|
||||
for (int i = 0; i < PUMPNET_LIB_FILE_TYPE_COUNT; i++) {
|
||||
for (int j = 0; j < PUMPNET_MAX_NUM_PLAYERS; j++) {
|
||||
_patch_net_profile_virtual_mnt_points[j].files[i].player_ref_id = 0;
|
||||
_patch_net_profile_virtual_mnt_points[j].files[i].handle = NULL;
|
||||
_patch_net_profile_virtual_mnt_points[j].files[i].file_info = NULL;
|
||||
_patch_net_profile_virtual_mnt_points[j].files[i].buffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
pumpnet_lib_init(game, pumpnet_server_addr, machine_id, cert_dir_path, verbose_debug_log);
|
||||
|
||||
cnh_filehook_push_handler(_patch_net_profile_filehook);
|
||||
|
||||
pthread_mutex_init(&_patch_net_profile_mutex, NULL);
|
||||
|
||||
log_info("Initialized: game %d, server %s, machine id %llX", game, pumpnet_server_addr, machine_id);
|
||||
}
|
||||
|
||||
void patch_net_profile_shutdown()
|
||||
{
|
||||
pumpnet_lib_shutdown();
|
||||
|
||||
pthread_mutex_destroy(&_patch_net_profile_mutex);
|
||||
|
||||
log_info("Shut down");
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Patch module to redirect profile saving and loading to a pumpnet server (NX2 to Fiesta 2).
|
||||
*/
|
||||
#ifndef HOOK_PATCH_NET_PROFILE_H
|
||||
#define HOOK_PATCH_NET_PROFILE_H
|
||||
|
||||
#include "pumpnet/lib/pumpnet.h"
|
||||
|
||||
/**
|
||||
* Initialize the module.
|
||||
*
|
||||
* @param game Game version to run this module on
|
||||
* @param pumpnet_server_addr Server address of pumpnet to connect to
|
||||
* @param machine_id Machine ID to use for authentication
|
||||
* @param cert_dir_path Path to a directory containing the client cert, client key and CA cert bundle to enable https
|
||||
* communication
|
||||
* @param verbose_debug_log Enable verbose debug log output, e.g. network backend logging/traffic.
|
||||
*/
|
||||
void patch_net_profile_init(
|
||||
enum asset_game_version game,
|
||||
const char* pumpnet_server_addr,
|
||||
uint64_t machine_id,
|
||||
const char* cert_dir_path,
|
||||
bool verbose_debug_log);
|
||||
|
||||
/**
|
||||
* Shut down the module.
|
||||
*/
|
||||
void patch_net_profile_shutdown();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,96 @@
|
||||
#define LOG_MODULE "patch-network"
|
||||
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "capnhook/hook/lib.h"
|
||||
|
||||
#include "util/list.h"
|
||||
#include "util/log.h"
|
||||
#include "util/mem.h"
|
||||
#include "util/str.h"
|
||||
|
||||
struct patch_network_redirect_elem {
|
||||
struct util_list_node head;
|
||||
char* orig_ipv4;
|
||||
char* new_ipv4;
|
||||
uint16_t new_port;
|
||||
};
|
||||
|
||||
typedef int (*connect_t) (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);
|
||||
|
||||
static connect_t patch_network_real_connect;
|
||||
|
||||
static bool patch_network_initialized;
|
||||
static struct util_list patch_network_redirect_list;
|
||||
|
||||
int connect(int fd, __CONST_SOCKADDR_ARG addr, socklen_t len)
|
||||
{
|
||||
if (!patch_network_real_connect) {
|
||||
patch_network_real_connect = (connect_t) cnh_lib_get_func_addr("connect");
|
||||
}
|
||||
|
||||
if (patch_network_initialized) {
|
||||
if (addr.__sockaddr__->sa_family == AF_INET) {
|
||||
log_debug("connect ipv4 %d.%d.%d.%d:%d",
|
||||
addr.__sockaddr_in__->sin_addr.s_addr & 0xFF,
|
||||
addr.__sockaddr_in__->sin_addr.s_addr >> 8 & 0xFF,
|
||||
addr.__sockaddr_in__->sin_addr.s_addr >> 16 & 0xFF,
|
||||
addr.__sockaddr_in__->sin_addr.s_addr >> 24 & 0xFF,
|
||||
addr.__sockaddr_in__->sin_port);
|
||||
|
||||
struct util_list_node* pos;
|
||||
struct patch_network_redirect_elem* elem;
|
||||
struct sockaddr_in tmp;
|
||||
|
||||
for (pos = patch_network_redirect_list.head; pos != NULL; pos = pos->next) {
|
||||
elem = containerof(pos, struct patch_network_redirect_elem, head);
|
||||
|
||||
inet_aton(elem->orig_ipv4, &tmp.sin_addr);
|
||||
|
||||
if (addr.__sockaddr_in__->sin_addr.s_addr == tmp.sin_addr.s_addr) {
|
||||
char* tmp_old = inet_ntoa(addr.__sockaddr_in__->sin_addr);
|
||||
log_info("Redirect %s:%d -> %s:%d", tmp_old,
|
||||
((struct sockaddr_in*) addr.__sockaddr_in__)->sin_port, elem->new_ipv4,
|
||||
elem->new_port != 0xFFFF ? elem->new_port :
|
||||
((struct sockaddr_in*) addr.__sockaddr_in__)->sin_port);
|
||||
|
||||
inet_aton(elem->new_ipv4, &((struct sockaddr_in*) addr.__sockaddr_in__)->sin_addr);
|
||||
|
||||
if (elem->new_port != 0xFFFF) {
|
||||
((struct sockaddr_in*) addr.__sockaddr_in__)->sin_port = htons(elem->new_port);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log_debug("connect, addr family: %d", addr.__sockaddr__->sa_family);
|
||||
}
|
||||
}
|
||||
|
||||
return patch_network_real_connect(fd, addr, len);
|
||||
}
|
||||
|
||||
void patch_network_init()
|
||||
{
|
||||
util_list_init(&patch_network_redirect_list);
|
||||
patch_network_initialized = true;
|
||||
log_info("Initialized");
|
||||
}
|
||||
|
||||
void patch_network_redirect_server_address(const char* orig_ipv4, const char* new_ipv4, uint16_t new_port)
|
||||
{
|
||||
struct patch_network_redirect_elem* elem;
|
||||
|
||||
elem = util_xmalloc(sizeof(struct patch_network_redirect_elem));
|
||||
elem->orig_ipv4 = util_str_dup(orig_ipv4);
|
||||
elem->new_ipv4 = util_str_dup(new_ipv4);
|
||||
elem->new_port = new_port;
|
||||
|
||||
util_list_append(&patch_network_redirect_list, &elem->head);
|
||||
|
||||
if (new_port != 0xFFFF) {
|
||||
log_info("Add network redirect: %s -> %s:%d", orig_ipv4, new_ipv4, new_port);
|
||||
} else {
|
||||
log_info("Add network redirect: %s -> %s", orig_ipv4, new_ipv4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Redirect network traffic for game server (Prime and up).
|
||||
*/
|
||||
#ifndef PATCH_NETWORK_H
|
||||
#define PATCH_NETWORK_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Initialize patch module
|
||||
*/
|
||||
void patch_network_init();
|
||||
|
||||
/**
|
||||
* Redirect a server address
|
||||
*
|
||||
* @param orig_ipv4 IPV4 address of the original server
|
||||
* @param new_ipv4 IPV4 address of the new server to redirect to
|
||||
* @param new_port Port of server to redirect to (-1 to keep old port)
|
||||
*/
|
||||
void patch_network_redirect_server_address(const char* orig_ipv4, const char* new_ipv4, uint16_t new_port);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,292 @@
|
||||
#define LOG_MODULE "patch-piubtn"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "capnhook/hook/lib.h"
|
||||
#include "capnhook/hooklib/usb-emu.h"
|
||||
|
||||
#include "io/piubtn/defs.h"
|
||||
|
||||
#include "ptapi/io/piubtn.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static bool _patch_piubtn_enumerate(bool real_exists);
|
||||
static enum cnh_result _patch_piubtn_open(void);
|
||||
static enum cnh_result _patch_piubtn_reset(void);
|
||||
static enum cnh_result _patch_piubtn_control_msg(int request_type, int request, int value, int index,
|
||||
struct cnh_iobuf* buffer, int timeout);
|
||||
static void _patch_piubtn_close(void);
|
||||
|
||||
static enum cnh_result _patch_piubtn_process_inputs(struct cnh_iobuf* buffer);
|
||||
static enum cnh_result _patch_piubtn_process_outputs(struct cnh_iobuf* buffer);
|
||||
|
||||
static ptapi_io_piubtn_ident_t _patch_piubtn_ptapi_io_piubtn_ident;
|
||||
static ptapi_io_piubtn_open_t _patch_piubtn_ptapi_io_piubtn_open;
|
||||
static ptapi_io_piubtn_close_t _patch_piubtn_ptapi_io_piubtn_close;
|
||||
static ptapi_io_piubtn_recv_t _patch_piubtn_ptapi_io_piubtn_recv;
|
||||
static ptapi_io_piubtn_send_t _patch_piubtn_ptapi_io_piubtn_send;
|
||||
static ptapi_io_piubtn_get_input_t _patch_piubtn_ptapi_io_piubtn_get_input;
|
||||
static ptapi_io_piubtn_set_output_t _patch_piubtn_ptapi_io_piubtn_set_output;
|
||||
|
||||
static const struct cnh_usb_emu_virtdev_ep _patch_piubtn_virtdev = {
|
||||
.pid = PIUBTN_DRV_PID,
|
||||
.vid = PIUBTN_DRV_VID,
|
||||
.enumerate = _patch_piubtn_enumerate,
|
||||
.open = _patch_piubtn_open,
|
||||
.reset = _patch_piubtn_reset,
|
||||
.control_msg = _patch_piubtn_control_msg,
|
||||
.close = _patch_piubtn_close,
|
||||
};
|
||||
|
||||
static void* _patch_piubtn_api_lib_handle;
|
||||
|
||||
void patch_piubtn_init(const char* piubtn_lib_path)
|
||||
{
|
||||
if (!piubtn_lib_path) {
|
||||
log_die("No piubtn emulation library path specified");
|
||||
}
|
||||
|
||||
/* Load piubtn library funcs */
|
||||
log_info("Loading piubtn api implementation %s", piubtn_lib_path);
|
||||
|
||||
_patch_piubtn_api_lib_handle = cnh_lib_load(piubtn_lib_path);
|
||||
|
||||
_patch_piubtn_ptapi_io_piubtn_ident = (ptapi_io_piubtn_ident_t)
|
||||
cnh_lib_get_func_addr_handle(_patch_piubtn_api_lib_handle, "ptapi_io_piubtn_ident");
|
||||
|
||||
_patch_piubtn_ptapi_io_piubtn_open = (ptapi_io_piubtn_open_t)
|
||||
cnh_lib_get_func_addr_handle(_patch_piubtn_api_lib_handle, "ptapi_io_piubtn_open");
|
||||
_patch_piubtn_ptapi_io_piubtn_close = (ptapi_io_piubtn_close_t)
|
||||
cnh_lib_get_func_addr_handle(_patch_piubtn_api_lib_handle, "ptapi_io_piubtn_close");
|
||||
_patch_piubtn_ptapi_io_piubtn_recv = (ptapi_io_piubtn_recv_t)
|
||||
cnh_lib_get_func_addr_handle(_patch_piubtn_api_lib_handle, "ptapi_io_piubtn_recv");
|
||||
_patch_piubtn_ptapi_io_piubtn_send = (ptapi_io_piubtn_send_t)
|
||||
cnh_lib_get_func_addr_handle(_patch_piubtn_api_lib_handle, "ptapi_io_piubtn_send");
|
||||
_patch_piubtn_ptapi_io_piubtn_get_input = (ptapi_io_piubtn_get_input_t)
|
||||
cnh_lib_get_func_addr_handle(_patch_piubtn_api_lib_handle, "ptapi_io_piubtn_get_input");
|
||||
_patch_piubtn_ptapi_io_piubtn_set_output = (ptapi_io_piubtn_set_output_t)
|
||||
cnh_lib_get_func_addr_handle(_patch_piubtn_api_lib_handle, "ptapi_io_piubtn_set_output");
|
||||
|
||||
cnh_usb_emu_add_virtdevep(&_patch_piubtn_virtdev);
|
||||
|
||||
log_info("Initialized");
|
||||
}
|
||||
|
||||
void patch_piubtn_shutdown(void)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static bool _patch_piubtn_enumerate(bool real_exists)
|
||||
{
|
||||
if (real_exists) {
|
||||
log_info("Real PIUBTN exists but emulation enabled, blocking real");
|
||||
} else {
|
||||
log_info("Enumerating PIUBTN emulation");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_piubtn_open(void)
|
||||
{
|
||||
log_info("Opening PIUBTN: %s", _patch_piubtn_ptapi_io_piubtn_ident());
|
||||
|
||||
if (!_patch_piubtn_ptapi_io_piubtn_open()) {
|
||||
log_error("Opening api piubtn %s failed", _patch_piubtn_ptapi_io_piubtn_ident());
|
||||
return CNH_RESULT_OTHER_ERROR;
|
||||
}
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_piubtn_reset(void)
|
||||
{
|
||||
log_info("Resetting PIUBTN: %s", _patch_piubtn_ptapi_io_piubtn_ident());
|
||||
|
||||
_patch_piubtn_ptapi_io_piubtn_close();
|
||||
|
||||
if (!_patch_piubtn_ptapi_io_piubtn_open()) {
|
||||
log_error("Resetting api piubtn %s failed", _patch_piubtn_ptapi_io_piubtn_ident());
|
||||
return CNH_RESULT_OTHER_ERROR;
|
||||
}
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_piubtn_control_msg(int request_type, int request, int value, int index,
|
||||
struct cnh_iobuf* buffer, int timeout)
|
||||
{
|
||||
if (request_type == PIUBTN_DRV_USB_CTRL_TYPE_IN && request == PIUBTN_DRV_USB_CTRL_REQUEST) {
|
||||
if (buffer->nbytes != PIUBTN_DRV_BUFFER_SIZE) {
|
||||
log_error("Invalid buffer size for ctrl in: %d", buffer->nbytes);
|
||||
return CNH_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
return _patch_piubtn_process_inputs(buffer);
|
||||
} else if (request_type == PIUBTN_DRV_USB_CTRL_TYPE_OUT && request == PIUBTN_DRV_USB_CTRL_REQUEST) {
|
||||
if (buffer->nbytes != PIUBTN_DRV_BUFFER_SIZE) {
|
||||
log_error("Invalid buffer size for ctrl out: %d", buffer->nbytes);
|
||||
return CNH_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
return _patch_piubtn_process_outputs(buffer);
|
||||
} else {
|
||||
log_error("Invalid usb control request to PIUBTN: 0x%02X", request);
|
||||
return CNH_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
static void _patch_piubtn_close(void)
|
||||
{
|
||||
log_info("Closing PIUBTN: %s", _patch_piubtn_ptapi_io_piubtn_ident());
|
||||
|
||||
_patch_piubtn_ptapi_io_piubtn_close();
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_piubtn_process_inputs(struct cnh_iobuf* buffer)
|
||||
{
|
||||
struct ptapi_io_piubtn_inputs p1;
|
||||
struct ptapi_io_piubtn_inputs p2;
|
||||
|
||||
if (!_patch_piubtn_ptapi_io_piubtn_recv()) {
|
||||
log_error("Receiving inputs on api piubtn %s failed", _patch_piubtn_ptapi_io_piubtn_ident());
|
||||
return CNH_RESULT_OTHER_ERROR;
|
||||
}
|
||||
|
||||
memset(&p1, 0, sizeof(p1));
|
||||
memset(&p2, 0, sizeof(p2));
|
||||
|
||||
_patch_piubtn_ptapi_io_piubtn_get_input(0, &p1);
|
||||
_patch_piubtn_ptapi_io_piubtn_get_input(1, &p2);
|
||||
|
||||
memset(buffer->bytes, 0, PIUBTN_DRV_BUFFER_SIZE);
|
||||
|
||||
/*
|
||||
byte 0:
|
||||
bit 0: P1 back (red)
|
||||
bit 1: P1 left
|
||||
bit 2: P1 right
|
||||
bit 3: P1 start (green)
|
||||
bit 4: P2 back (red)
|
||||
bit 5: P2 left
|
||||
bit 6: P2 right
|
||||
bit 7: P2 start (green)
|
||||
*/
|
||||
|
||||
/* Player 1 */
|
||||
if (p1.back) {
|
||||
buffer->bytes[0] |= (1 << 0);
|
||||
}
|
||||
|
||||
if (p1.left) {
|
||||
buffer->bytes[0] |= (1 << 1);
|
||||
}
|
||||
|
||||
if (p1.right) {
|
||||
buffer->bytes[0] |= (1 << 2);
|
||||
}
|
||||
|
||||
if (p1.start) {
|
||||
buffer->bytes[0] |= (1 << 3);
|
||||
}
|
||||
|
||||
/* Player 2 */
|
||||
if (p2.back) {
|
||||
buffer->bytes[0] |= (1 << 4);
|
||||
}
|
||||
|
||||
if (p2.left) {
|
||||
buffer->bytes[0] |= (1 << 5);
|
||||
}
|
||||
|
||||
if (p2.right) {
|
||||
buffer->bytes[0] |= (1 << 6);
|
||||
}
|
||||
|
||||
if (p2.start) {
|
||||
buffer->bytes[0] |= (1 << 7);
|
||||
}
|
||||
|
||||
/* xor inputs because pullup active */
|
||||
for (int i = 0; i < PIUBTN_DRV_BUFFER_SIZE; i++) {
|
||||
buffer->bytes[i] ^= 0xFF;
|
||||
}
|
||||
|
||||
buffer->pos = PIUBTN_DRV_BUFFER_SIZE;
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_piubtn_process_outputs(struct cnh_iobuf* buffer)
|
||||
{
|
||||
struct ptapi_io_piubtn_outputs p1;
|
||||
struct ptapi_io_piubtn_outputs p2;
|
||||
|
||||
memset(&p1, 0, sizeof(p1));
|
||||
memset(&p2, 0, sizeof(p2));
|
||||
|
||||
/*
|
||||
byte 0:
|
||||
bit 0: p1 start (green)
|
||||
bit 1: p1 right
|
||||
bit 2: p1 left
|
||||
bit 3: p1 back (red)
|
||||
bit 4: p2 start (green)
|
||||
bit 5: p2 right
|
||||
bit 6: p2 left
|
||||
bit 7: p2 back (red)
|
||||
*/
|
||||
|
||||
/* Player 1 */
|
||||
|
||||
if (buffer->bytes[0] & (1 << 0)) {
|
||||
p1.start = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[0] & (1 << 1)) {
|
||||
p1.back = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[0] & (1 << 2)) {
|
||||
p1.left = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[0] & (1 << 3)) {
|
||||
p1.right = true;
|
||||
}
|
||||
|
||||
/* Player 2 */
|
||||
|
||||
if (buffer->bytes[0] & (1 << 4)) {
|
||||
p2.start = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[0] & (1 << 5)) {
|
||||
p2.right = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[0] & (1 << 6)) {
|
||||
p2.left = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[0] & (1 << 7)) {
|
||||
p2.back = true;
|
||||
}
|
||||
|
||||
_patch_piubtn_ptapi_io_piubtn_set_output(0, &p1);
|
||||
_patch_piubtn_ptapi_io_piubtn_set_output(1, &p2);
|
||||
|
||||
if (!_patch_piubtn_ptapi_io_piubtn_send()) {
|
||||
log_error("Sending outputs on api piubtn %s failed", _patch_piubtn_ptapi_io_piubtn_ident());
|
||||
return CNH_RESULT_OTHER_ERROR;
|
||||
}
|
||||
|
||||
buffer->pos = PIUBTN_DRV_BUFFER_SIZE;
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Patch libusb-0.1 calls to intercept with piubtn (pump pro buttons)
|
||||
* communication and hook our API with emulation code
|
||||
*/
|
||||
#ifndef PATCH_PIUBTN_H
|
||||
#define PATCH_PIUBTN_H
|
||||
|
||||
#include "ptapi/io/x11-input-hook.h"
|
||||
|
||||
#include "util/options.h"
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*
|
||||
* @param piubtn_lib_path Path to a library implementing the piubtn API
|
||||
* @param real_passthrough True to ignore the library and disable any emulation
|
||||
*/
|
||||
void patch_piubtn_init(const char* piubtn_lib_path);
|
||||
|
||||
/**
|
||||
* Get the input hook handler provided by the piubtn library.
|
||||
*
|
||||
* @return Input hook handler for main-loop patch module or NULL if none is used.
|
||||
*/
|
||||
const struct pumptools_input_hook_handler* patch_piubtn_get_input_hook_handler(void);
|
||||
|
||||
/**
|
||||
* Shut down the patch module
|
||||
*/
|
||||
void patch_piubtn_shutdown(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,87 @@
|
||||
|
||||
#include "capnhook/hook/usbhook.h"
|
||||
|
||||
#include "io/piuio/defs.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static enum cnh_result patch_piuio_exit_usbhook(struct cnh_usbhook_irp *irp);
|
||||
|
||||
static usb_dev_handle* _patch_piuio_exit_usb_handle;
|
||||
|
||||
void patch_piuio_exit_init()
|
||||
{
|
||||
cnh_usbhook_push_handler(patch_piuio_exit_usbhook);
|
||||
log_info("Initialized");
|
||||
}
|
||||
|
||||
static enum cnh_result patch_piuio_exit_usbhook(struct cnh_usbhook_irp *irp)
|
||||
{
|
||||
enum cnh_result result;
|
||||
|
||||
if (irp->op != CNH_USBHOOK_IRP_OP_OPEN && _patch_piuio_exit_usb_handle != irp->handle) {
|
||||
return cnh_usbhook_invoke_next(irp);
|
||||
}
|
||||
|
||||
switch (irp->op) {
|
||||
case CNH_USBHOOK_IRP_OP_OPEN:
|
||||
{
|
||||
if (_patch_piuio_exit_usb_handle) {
|
||||
log_warn("Already detected PIUIO previously, ignore re-detection?");
|
||||
result = cnh_usbhook_invoke_next(irp);
|
||||
break;
|
||||
}
|
||||
|
||||
if (irp->open_usb_dev->descriptor.idVendor == PIUIO_DRV_VID &&
|
||||
irp->open_usb_dev->descriptor.idProduct == PIUIO_DRV_PID) {
|
||||
log_info("Detected PIUIO");
|
||||
|
||||
result = cnh_usbhook_invoke_next(irp);
|
||||
|
||||
if (result == CNH_RESULT_SUCCESS) {
|
||||
_patch_piuio_exit_usb_handle = irp->handle;
|
||||
}
|
||||
} else {
|
||||
result = cnh_usbhook_invoke_next(irp);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_USBHOOK_IRP_OP_CTRL_MSG:
|
||||
{
|
||||
result = cnh_usbhook_invoke_next(irp);
|
||||
|
||||
/* Post process inputs */
|
||||
if (irp->ctrl_req_type == PIUIO_DRV_USB_CTRL_TYPE_IN && irp->ctrl_req == PIUIO_DRV_USB_CTRL_REQUEST) {
|
||||
if (irp->ctrl_buffer.nbytes != PIUIO_DRV_BUFFER_SIZE) {
|
||||
log_error("Invalid buffer size for ctrl in: %d", irp->ctrl_buffer.nbytes);
|
||||
} else {
|
||||
/* Service + Test button pressed */
|
||||
/* Invert data -> pull up active */
|
||||
if (((~irp->ctrl_buffer.bytes[1]) & (1 << 1)) && ((~irp->ctrl_buffer.bytes[1]) & (1 << 6))) {
|
||||
log_info("Exit on service + test enabled and hit, bye");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CNH_USBHOOK_IRP_OP_CLOSE:
|
||||
{
|
||||
_patch_piuio_exit_usb_handle = NULL;
|
||||
|
||||
result = cnh_usbhook_invoke_next(irp);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
result = cnh_usbhook_invoke_next(irp);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Patch module to allow exiting the game using the PIUIO, e.g. pressing service + test.
|
||||
*/
|
||||
#ifndef PATCH_PIUIO_EXIT_H
|
||||
#define PATCH_PIUIO_EXIT_H
|
||||
|
||||
/**
|
||||
* Enable the module
|
||||
*/
|
||||
void patch_piuio_exit_init();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,572 @@
|
||||
#define LOG_MODULE "patch-piuio"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "capnhook/hooklib/usb-emu.h"
|
||||
|
||||
#include "io/piuio/defs.h"
|
||||
|
||||
#include "ptapi/io/piuio.h"
|
||||
#include "ptapi/io/piuio/util/lib.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static bool _patch_piuio_enumerate(bool real_exists);
|
||||
static enum cnh_result _patch_piuio_open(void);
|
||||
static enum cnh_result _patch_piuio_reset(void);
|
||||
static enum cnh_result _patch_piuio_control_msg(int request_type, int request, int value, int index,
|
||||
struct cnh_iobuf* buffer, int timeout);
|
||||
static void _patch_piuio_close(void);
|
||||
|
||||
static enum cnh_result _patch_piuio_process_inputs(struct cnh_iobuf* buffer);
|
||||
static enum cnh_result _patch_piuio_process_outputs(struct cnh_iobuf* buffer);
|
||||
static enum cnh_result _patch_piuio_process_itg_kernel_module_hack(struct cnh_iobuf* buffer);
|
||||
|
||||
static const struct cnh_usb_emu_virtdev_ep _patch_piuio_virtdev = {
|
||||
.pid = PIUIO_DRV_PID,
|
||||
.vid = PIUIO_DRV_VID,
|
||||
.enumerate = _patch_piuio_enumerate,
|
||||
.open = _patch_piuio_open,
|
||||
.reset = _patch_piuio_reset,
|
||||
.control_msg = _patch_piuio_control_msg,
|
||||
.close = _patch_piuio_close,
|
||||
};
|
||||
|
||||
static struct ptapi_io_piuio_api _patch_piuio_api;
|
||||
static enum ptapi_io_piuio_sensor_group _patch_piuio_sensor_group;
|
||||
|
||||
void patch_piuio_init(const char* piuio_lib_path)
|
||||
{
|
||||
if (!piuio_lib_path) {
|
||||
log_die("No piuio emulation library path specified");
|
||||
}
|
||||
|
||||
/* Load piuio library funcs */
|
||||
log_info("Loading piuio api implementation %s", piuio_lib_path);
|
||||
|
||||
if (!ptapi_io_piuio_util_lib_load(piuio_lib_path, &_patch_piuio_api)) {
|
||||
log_error("Loading PIUIO API from %s failed", piuio_lib_path);
|
||||
return;
|
||||
}
|
||||
|
||||
cnh_usb_emu_add_virtdevep(&_patch_piuio_virtdev);
|
||||
|
||||
log_info("Initialized");
|
||||
}
|
||||
|
||||
void patch_piuio_shutdown(void)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static bool _patch_piuio_enumerate(bool real_exists)
|
||||
{
|
||||
if (real_exists) {
|
||||
log_info("Real PIUIO exists but emulation enabled, blocking real");
|
||||
} else {
|
||||
log_info("Enumerating PIUIO emulation");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_piuio_open(void)
|
||||
{
|
||||
log_info("Opening PIUIO: %s", _patch_piuio_api.ident());
|
||||
|
||||
if (!_patch_piuio_api.open()) {
|
||||
log_error("Opening api piuio %s failed", _patch_piuio_api.ident());
|
||||
return CNH_RESULT_OTHER_ERROR;
|
||||
}
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_piuio_reset(void)
|
||||
{
|
||||
log_info("Resetting PIUIO: %s", _patch_piuio_api.ident());
|
||||
|
||||
_patch_piuio_api.close();
|
||||
|
||||
if (!_patch_piuio_api.open()) {
|
||||
log_error("Resetting api piuio %s failed", _patch_piuio_api.ident());
|
||||
return CNH_RESULT_OTHER_ERROR;
|
||||
}
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_piuio_control_msg(int request_type, int request, int value, int index,
|
||||
struct cnh_iobuf* buffer, int timeout)
|
||||
{
|
||||
if (request_type == PIUIO_DRV_USB_CTRL_TYPE_IN && request == PIUIO_DRV_USB_CTRL_REQUEST) {
|
||||
if (buffer->nbytes != PIUIO_DRV_BUFFER_SIZE) {
|
||||
log_error("Invalid buffer size for ctrl in: %d", buffer->nbytes);
|
||||
return CNH_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
return _patch_piuio_process_inputs(buffer);
|
||||
} else if (request_type == PIUIO_DRV_USB_CTRL_TYPE_OUT && request == PIUIO_DRV_USB_CTRL_REQUEST) {
|
||||
if (buffer->nbytes != PIUIO_DRV_BUFFER_SIZE) {
|
||||
log_error("Invalid buffer size for ctrl out: %d", buffer->nbytes);
|
||||
return CNH_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
return _patch_piuio_process_outputs(buffer);
|
||||
} else if (request_type == 0 && request == 0) {
|
||||
/* ITG 2/PIU Pro kernel hack */
|
||||
if (buffer->nbytes != PIUIO_DRV_BUFFER_SIZE * 4) {
|
||||
log_error("Invalid buffer size for kernel hack: %d", buffer->nbytes);
|
||||
return CNH_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
return _patch_piuio_process_itg_kernel_module_hack(buffer);
|
||||
} else {
|
||||
log_error("Invalid usb control request to PIUIO: 0x%02X", request);
|
||||
return CNH_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
static void _patch_piuio_close(void)
|
||||
{
|
||||
log_info("Closing PIUIO: %s", _patch_piuio_api.ident());
|
||||
|
||||
_patch_piuio_api.close();
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_piuio_process_inputs(struct cnh_iobuf* buffer)
|
||||
{
|
||||
struct ptapi_io_piuio_pad_inputs p1_pad_in;
|
||||
struct ptapi_io_piuio_pad_inputs p2_pad_in;
|
||||
struct ptapi_io_piuio_sys_inputs sys_in;
|
||||
|
||||
if (!_patch_piuio_api.recv()) {
|
||||
log_error("Receiving inputs on api piuio %s failed", _patch_piuio_api.ident());
|
||||
return CNH_RESULT_OTHER_ERROR;
|
||||
}
|
||||
|
||||
memset(&p1_pad_in, 0, sizeof(struct ptapi_io_piuio_pad_inputs));
|
||||
memset(&p2_pad_in, 0, sizeof(struct ptapi_io_piuio_pad_inputs));
|
||||
memset(&sys_in, 0, sizeof(struct ptapi_io_piuio_sys_inputs));
|
||||
|
||||
_patch_piuio_api.get_input_pad(0, _patch_piuio_sensor_group, &p1_pad_in);
|
||||
_patch_piuio_api.get_input_pad(1, _patch_piuio_sensor_group, &p2_pad_in);
|
||||
|
||||
_patch_piuio_api.get_input_sys(&sys_in);
|
||||
|
||||
/*
|
||||
byte 0:
|
||||
bit 0: sensor p1: LU
|
||||
bit 1: sensor p1: RU
|
||||
bit 2: sensor p1: CN
|
||||
bit 3: sensor p1: LD
|
||||
bit 4: sensor p1: RD
|
||||
bit 5:
|
||||
bit 6:
|
||||
bit 7:
|
||||
|
||||
byte 1:
|
||||
bit 0:
|
||||
bit 1: test
|
||||
bit 2: coin 1
|
||||
bit 3:
|
||||
bit 4:
|
||||
bit 5:
|
||||
bit 6: service
|
||||
bit 7: clear
|
||||
|
||||
byte 2:
|
||||
bit 0: sensor p2: LU
|
||||
bit 1: sensor p2: RU
|
||||
bit 2: sensor p2: CN
|
||||
bit 3: sensor p2: LD
|
||||
bit 4: sensor p2: RD
|
||||
bit 5:
|
||||
bit 6:
|
||||
bit 7:
|
||||
|
||||
byte 3:
|
||||
bit 0:
|
||||
bit 1:
|
||||
bit 2: coin 2
|
||||
bit 3:
|
||||
bit 4:
|
||||
bit 5:
|
||||
bit 6:
|
||||
bit 7:
|
||||
|
||||
bytes 4 - 7 dummy
|
||||
*/
|
||||
|
||||
/* Player 1 */
|
||||
buffer->bytes[0] = 0;
|
||||
|
||||
if (p1_pad_in.lu) {
|
||||
buffer->bytes[0] |= (1 << 0);
|
||||
}
|
||||
|
||||
if (p1_pad_in.ru) {
|
||||
buffer->bytes[0] |= (1 << 1);
|
||||
}
|
||||
|
||||
if (p1_pad_in.cn) {
|
||||
buffer->bytes[0] |= (1 << 2);
|
||||
}
|
||||
|
||||
if (p1_pad_in.ld) {
|
||||
buffer->bytes[0] |= (1 << 3);
|
||||
}
|
||||
|
||||
if (p1_pad_in.rd) {
|
||||
buffer->bytes[0] |= (1 << 4);
|
||||
}
|
||||
|
||||
buffer->bytes[0] ^= 0xFF;
|
||||
|
||||
buffer->bytes[2] = 0;
|
||||
|
||||
/* Player 2 */
|
||||
if (p2_pad_in.lu) {
|
||||
buffer->bytes[2] |= (1 << 0);
|
||||
}
|
||||
|
||||
if (p2_pad_in.ru) {
|
||||
buffer->bytes[2] |= (1 << 1);
|
||||
}
|
||||
|
||||
if (p2_pad_in.cn) {
|
||||
buffer->bytes[2] |= (1 << 2);
|
||||
}
|
||||
|
||||
if (p2_pad_in.ld) {
|
||||
buffer->bytes[2] |= (1 << 3);
|
||||
}
|
||||
|
||||
if (p2_pad_in.rd) {
|
||||
buffer->bytes[2] |= (1 << 4);
|
||||
}
|
||||
|
||||
buffer->bytes[2] ^= 0xFF;
|
||||
|
||||
buffer->bytes[1] = 0;
|
||||
|
||||
/* Sys */
|
||||
if (sys_in.test) {
|
||||
buffer->bytes[1] |= (1 << 1);
|
||||
}
|
||||
|
||||
if (sys_in.service) {
|
||||
buffer->bytes[1] |= (1 << 6);
|
||||
}
|
||||
|
||||
if (sys_in.clear) {
|
||||
buffer->bytes[1] |= (1 << 7);
|
||||
}
|
||||
|
||||
if (sys_in.coin) {
|
||||
buffer->bytes[1] |= (1 << 2);
|
||||
}
|
||||
|
||||
buffer->bytes[1] ^= 0xFF;
|
||||
|
||||
/* Apparently, "touching" byte 3 as a whole causes some random and weird input triggering
|
||||
which results in the service menu popping up. Don't clear the whole byte, touch coin2 only to avoid this */
|
||||
if (!sys_in.coin2) {
|
||||
buffer->bytes[3] |= (1 << 2);
|
||||
} else {
|
||||
buffer->bytes[3] &= ~(1 << 2);
|
||||
}
|
||||
|
||||
buffer->pos = PIUIO_DRV_BUFFER_SIZE;
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_piuio_process_outputs(struct cnh_iobuf* buffer)
|
||||
{
|
||||
struct ptapi_io_piuio_pad_outputs p1_pad_out;
|
||||
struct ptapi_io_piuio_pad_outputs p2_pad_out;
|
||||
struct ptapi_io_piuio_cab_outputs cab_out;
|
||||
|
||||
memset(&p1_pad_out, 0, sizeof(struct ptapi_io_piuio_pad_outputs));
|
||||
memset(&p2_pad_out, 0, sizeof(struct ptapi_io_piuio_pad_outputs));
|
||||
memset(&cab_out, 0, sizeof(struct ptapi_io_piuio_cab_outputs));
|
||||
|
||||
/*
|
||||
byte 0:
|
||||
bit 0: sensor id bit 0 select sensor to be read for next input request:
|
||||
bit 1: sensor id bit 1 00 = sens1, 01 = sens2, 10 = sens3, 11 = sens4
|
||||
bit 2: pad light p1: LU
|
||||
bit 3: pad light p1: RU
|
||||
bit 4: pad light p1: CN
|
||||
bit 5: pad light p1: LD
|
||||
bit 6: pad light p1: RD
|
||||
bit 7:
|
||||
|
||||
byte 1:
|
||||
bit 0:
|
||||
bit 1:
|
||||
bit 2: light neons
|
||||
bit 3: coin counter 2 (front usb ports enable)
|
||||
bit 4:
|
||||
bit 5:
|
||||
bit 6:
|
||||
bit 7:
|
||||
|
||||
byte 2:
|
||||
bit 0:
|
||||
bit 1:
|
||||
bit 2: pad light p2: LU
|
||||
bit 3: pad light p2: RU
|
||||
bit 4: pad light p2: CN
|
||||
bit 5: pad light p2: LD
|
||||
bit 6: pad light p2: RD
|
||||
bit 7: halogen R2
|
||||
|
||||
byte 3:
|
||||
bit 0: halogen R1
|
||||
bit 1: halogen L2
|
||||
bit 2: halogen L1
|
||||
bit 3:
|
||||
bit 4: coin counter 1
|
||||
bit 5:
|
||||
bit 6:
|
||||
bit 7:
|
||||
|
||||
bytes 4 - 7 dummy
|
||||
*/
|
||||
|
||||
_patch_piuio_sensor_group = (enum ptapi_io_piuio_sensor_group) (buffer->bytes[0] & 0x03);
|
||||
|
||||
/* Player 1 */
|
||||
|
||||
if (buffer->bytes[0] & (1 << 2)) {
|
||||
p1_pad_out.lu = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[0] & (1 << 3)) {
|
||||
p1_pad_out.ru = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[0] & (1 << 4)) {
|
||||
p1_pad_out.cn = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[0] & (1 << 5)) {
|
||||
p1_pad_out.ld = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[0] & (1 << 6)) {
|
||||
p1_pad_out.rd = true;
|
||||
}
|
||||
|
||||
/* Player 2 */
|
||||
|
||||
if (buffer->bytes[2] & (1 << 2)) {
|
||||
p2_pad_out.lu = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[2] & (1 << 3)) {
|
||||
p2_pad_out.ru = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[2] & (1 << 4)) {
|
||||
p2_pad_out.cn = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[2] & (1 << 5)) {
|
||||
p2_pad_out.ld = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[2] & (1 << 6)) {
|
||||
p2_pad_out.rd = true;
|
||||
}
|
||||
|
||||
/* Cabinet */
|
||||
|
||||
if (buffer->bytes[1] & (1 << 2)) {
|
||||
cab_out.bass = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[2] & (1 << 7)) {
|
||||
cab_out.halo_r2 = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[3] & (1 << 0)) {
|
||||
cab_out.halo_r1 = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[3] & (1 << 1)) {
|
||||
cab_out.halo_l2 = true;
|
||||
}
|
||||
|
||||
if (buffer->bytes[3] & (1 << 2)) {
|
||||
cab_out.halo_l1 = true;
|
||||
}
|
||||
|
||||
_patch_piuio_api.set_output_pad(0, &p1_pad_out);
|
||||
_patch_piuio_api.set_output_pad(1, &p2_pad_out);
|
||||
_patch_piuio_api.set_output_cab(&cab_out);
|
||||
|
||||
if (!_patch_piuio_api.send()) {
|
||||
log_error("Sending outputs on api piuio %s failed", _patch_piuio_api.ident());
|
||||
return CNH_RESULT_OTHER_ERROR;
|
||||
}
|
||||
|
||||
buffer->pos = PIUIO_DRV_BUFFER_SIZE;
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_piuio_process_itg_kernel_module_hack(struct cnh_iobuf* buffer)
|
||||
{
|
||||
/* Instead of calling four times, one call for each sensor set, a special
|
||||
kernel module is loaded which traps the following call:
|
||||
usb_control_msg(dev_handle, 0, 0, 0, 0, buffer, 32, 10011);
|
||||
All four sensor sets are polled in a single context switch and returned
|
||||
as one big buffer instead of four smalle ones (8 bytes each).
|
||||
Pump Pro relies on this, so we have to support it here */
|
||||
|
||||
struct ptapi_io_piuio_pad_inputs p1_pad_in[4];
|
||||
struct ptapi_io_piuio_pad_inputs p2_pad_in[4];
|
||||
struct ptapi_io_piuio_sys_inputs sys_in;
|
||||
|
||||
/* The output duplicated 4 times (4 * 8) in the buffer. Write it once */
|
||||
_patch_piuio_process_outputs(buffer);
|
||||
|
||||
if (!_patch_piuio_api.recv()) {
|
||||
log_error("Receiving inputs on api piuio %s failed", _patch_piuio_api.ident());
|
||||
return CNH_RESULT_OTHER_ERROR;
|
||||
}
|
||||
|
||||
memset(buffer->bytes, 0, PIUIO_DRV_BUFFER_SIZE * 4);
|
||||
|
||||
memset(&sys_in, 0, sizeof(struct ptapi_io_piuio_sys_inputs));
|
||||
|
||||
for (uint8_t i = 0; i < 4; i++) {
|
||||
memset(&p1_pad_in[i], 0, sizeof(struct ptapi_io_piuio_pad_inputs));
|
||||
memset(&p2_pad_in[i], 0, sizeof(struct ptapi_io_piuio_pad_inputs));
|
||||
|
||||
_patch_piuio_api.get_input_pad(0, (enum ptapi_io_piuio_sensor_group) i, &p1_pad_in[i]);
|
||||
_patch_piuio_api.get_input_pad(1, (enum ptapi_io_piuio_sensor_group) i, &p2_pad_in[i]);
|
||||
}
|
||||
|
||||
_patch_piuio_api.get_input_sys(&sys_in);
|
||||
|
||||
/*
|
||||
byte 0:
|
||||
bit 0: sensor p1: LU
|
||||
bit 1: sensor p1: RU
|
||||
bit 2: sensor p1: CN
|
||||
bit 3: sensor p1: LD
|
||||
bit 4: sensor p1: RD
|
||||
bit 5:
|
||||
bit 6:
|
||||
bit 7:
|
||||
|
||||
byte 1:
|
||||
bit 0:
|
||||
bit 1: test
|
||||
bit 2: coin 1
|
||||
bit 3:
|
||||
bit 4:
|
||||
bit 5:
|
||||
bit 6: service
|
||||
bit 7: clear
|
||||
|
||||
byte 2:
|
||||
bit 0: sensor p2: LU
|
||||
bit 1: sensor p2: RU
|
||||
bit 2: sensor p2: CN
|
||||
bit 3: sensor p2: LD
|
||||
bit 4: sensor p2: RD
|
||||
bit 5:
|
||||
bit 6:
|
||||
bit 7:
|
||||
|
||||
byte 3:
|
||||
bit 0:
|
||||
bit 1:
|
||||
bit 2: coin 2
|
||||
bit 3:
|
||||
bit 4:
|
||||
bit 5:
|
||||
bit 6:
|
||||
bit 7:
|
||||
|
||||
bytes 4 - 7 dummy
|
||||
*/
|
||||
|
||||
for (uint8_t i = 0; i < 4; i++) {
|
||||
/* Player 1 */
|
||||
if (p1_pad_in[i].lu) {
|
||||
buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 0] |= (1 << 0);
|
||||
}
|
||||
|
||||
if (p1_pad_in[i].ru) {
|
||||
buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 0] |= (1 << 1);
|
||||
}
|
||||
|
||||
if (p1_pad_in[i].cn) {
|
||||
buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 0] |= (1 << 2);
|
||||
}
|
||||
|
||||
if (p1_pad_in[i].ld) {
|
||||
buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 0] |= (1 << 3);
|
||||
}
|
||||
|
||||
if (p1_pad_in[i].rd) {
|
||||
buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 0] |= (1 << 4);
|
||||
}
|
||||
|
||||
/* Player 2 */
|
||||
if (p2_pad_in[i].lu) {
|
||||
buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 2] |= (1 << 0);
|
||||
}
|
||||
|
||||
if (p2_pad_in[i].ru) {
|
||||
buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 2] |= (1 << 1);
|
||||
}
|
||||
|
||||
if (p2_pad_in[i].cn) {
|
||||
buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 2] |= (1 << 2);
|
||||
}
|
||||
|
||||
if (p2_pad_in[i].ld) {
|
||||
buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 2] |= (1 << 3);
|
||||
}
|
||||
|
||||
if (p2_pad_in[i].rd) {
|
||||
buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 2] |= (1 << 4);
|
||||
}
|
||||
|
||||
/* Sys */
|
||||
if (sys_in.test) {
|
||||
buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 1] |= (1 << 1);
|
||||
}
|
||||
|
||||
if (sys_in.service) {
|
||||
buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 1] |= (1 << 6);
|
||||
}
|
||||
|
||||
if (sys_in.clear) {
|
||||
buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 1] |= (1 << 7);
|
||||
}
|
||||
|
||||
if (sys_in.coin) {
|
||||
buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 1] |= (1 << 2);
|
||||
}
|
||||
}
|
||||
|
||||
/* xor inputs because pullup active */
|
||||
for (int i = 0; i < PIUIO_DRV_BUFFER_SIZE * 4; i++) {
|
||||
buffer->bytes[i] ^= 0xFF;
|
||||
}
|
||||
|
||||
buffer->nbytes = PIUIO_DRV_BUFFER_SIZE * 4;
|
||||
|
||||
return CNH_RESULT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Patch libusb-0.1 calls to intercept with piuio communication and hook
|
||||
* our API with emulation code
|
||||
*/
|
||||
#ifndef PATCH_PIUIO_H
|
||||
#define PATCH_PIUIO_H
|
||||
|
||||
#include "util/options.h"
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*
|
||||
* @param piuio_lib_path Path to a library implementing the piuio API
|
||||
*/
|
||||
void patch_piuio_init(const char* piuio_lib_path);
|
||||
|
||||
/**
|
||||
* Shut down the patch module
|
||||
*/
|
||||
void patch_piuio_shutdown(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,25 @@
|
||||
#define LOG_MODULE "patch-ram-wipe"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "capnhook/hook/filehook.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static enum cnh_result _patch_ram_wipe_filehook(struct cnh_filehook_irp *irp);
|
||||
|
||||
void patch_ram_wipe_init(void)
|
||||
{
|
||||
cnh_filehook_push_handler(_patch_ram_wipe_filehook);
|
||||
log_info("Initialized");
|
||||
}
|
||||
|
||||
static enum cnh_result _patch_ram_wipe_filehook(struct cnh_filehook_irp *irp)
|
||||
{
|
||||
if (irp->op == CNH_FILEHOOK_IRP_OP_OPEN && !strcmp(irp->open_filename, "/dev/ram0")) {
|
||||
log_info("Killing ram wipe");
|
||||
return CNH_RESULT_NO_SUCH_FILE_OR_DIR;
|
||||
} else {
|
||||
return cnh_filehook_invoke_next(irp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Starting with Prime the game executes a ram wipe. We don't want that
|
||||
* to happen.
|
||||
*/
|
||||
#ifndef PATCH_RAM_WIPE_H
|
||||
#define PATCH_RAM_WIPE_H
|
||||
|
||||
/**
|
||||
* Initialize patch module
|
||||
*/
|
||||
void patch_ram_wipe_init(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
#define LOG_MODULE "patch-redir"
|
||||
|
||||
#include "capnhook/hooklib/redir.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
void patch_redir_init()
|
||||
{
|
||||
cnh_fshook_push_handler(cnh_redir_fshook);
|
||||
cnh_filehook_push_handler(cnh_redir_filehook);
|
||||
cnh_iohook_push_handler(cnh_redir_iohook);
|
||||
|
||||
log_info("Initialized");
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Module to enable the redirect hook module to redirect file/device open paths.
|
||||
*/
|
||||
#ifndef PATCH_REDIR_H
|
||||
#define PATCH_REDIR_H
|
||||
|
||||
/**
|
||||
* Enable module
|
||||
*/
|
||||
void patch_redir_init();
|
||||
|
||||
#endif //PATCH_REDIR_H
|
||||
@@ -0,0 +1,53 @@
|
||||
#define LOG_MODULE "patch-sigsegv"
|
||||
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "capnhook/hook/sig.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#include "sigsegv.h"
|
||||
|
||||
static bool patch_sigsevc_halt_on_segv;
|
||||
|
||||
static void patch_sigsegv_handler(int signal, __sighandler_t orig_handler)
|
||||
{
|
||||
log_error("===================");
|
||||
log_error("!!!!! SIGSEGV !!!!!");
|
||||
|
||||
void* array[128];
|
||||
size_t size;
|
||||
char** strings;
|
||||
|
||||
size = (size_t) backtrace(array, sizeof(array));
|
||||
strings = backtrace_symbols(array, size);
|
||||
|
||||
log_error("Backtrace (%d frames):", size);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
log_error("%s", strings[i]);
|
||||
}
|
||||
|
||||
free(strings);
|
||||
|
||||
if (patch_sigsevc_halt_on_segv) {
|
||||
log_error("Halting for debugger...");
|
||||
|
||||
while (true) {
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
void patch_sigsegv_init(bool halt_on_segv)
|
||||
{
|
||||
patch_sigsevc_halt_on_segv = halt_on_segv;
|
||||
|
||||
cnh_sig_install(SIGSEGV, patch_sigsegv_handler);
|
||||
log_info("Initialized");
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Install a proper signal handler to enhance error output on crashes
|
||||
*/
|
||||
#ifndef PATCH_SIGSEGV_H
|
||||
#define PATCH_SIGSEGV_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*
|
||||
* @param halt_on_segv Halt on segv to attach a debugger to the application
|
||||
*/
|
||||
void patch_sigsegv_init(bool halt_on_segv);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,66 @@
|
||||
#define LOG_MODULE "patch-sound"
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <grp.h>
|
||||
|
||||
#include "capnhook/hook/lib.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
typedef int (*snd_pcm_open_t)(snd_pcm_t** pcmp, const char* name,
|
||||
snd_pcm_stream_t stream, int mode);
|
||||
|
||||
static snd_pcm_open_t patch_sound_real_snd_pcm_open;
|
||||
|
||||
static const char* patch_sound_dev_name;
|
||||
|
||||
int snd_pcm_open(snd_pcm_t** pcmp, const char* name, snd_pcm_stream_t stream, int mode)
|
||||
{
|
||||
int ret = 0;
|
||||
int retry_count = 5;
|
||||
|
||||
log_debug("snd_pcm_open: %s, stream %d, mode %d", name, stream, mode);
|
||||
|
||||
if (!patch_sound_real_snd_pcm_open) {
|
||||
patch_sound_real_snd_pcm_open = (snd_pcm_open_t) cnh_lib_get_func_addr("snd_pcm_open");
|
||||
}
|
||||
|
||||
if (patch_sound_dev_name) {
|
||||
log_info("Opening sound device %s instead of %s", patch_sound_dev_name, name);
|
||||
|
||||
name = patch_sound_dev_name;
|
||||
}
|
||||
|
||||
/*
|
||||
Notes about exceed and how it fucked up using alsa:
|
||||
- Opening the same (hw:0) device multiple times without closing
|
||||
- Opening and closing the device on each effect play
|
||||
- No proper error handling on snd_pcm_open return
|
||||
*/
|
||||
|
||||
/* Using dmix instead of hw:0 on exceed, snd_pcm_open sometimes returns
|
||||
with a bad fd but after another retry, everything's fine */
|
||||
while (retry_count > 0) {
|
||||
ret = patch_sound_real_snd_pcm_open(pcmp, name, stream, mode);
|
||||
|
||||
if (ret < 0) {
|
||||
retry_count--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
log_error("snd_pcm_open failed: %s", snd_strerror(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void patch_sound_init(const char* dev_name)
|
||||
{
|
||||
patch_sound_dev_name = dev_name;
|
||||
|
||||
log_info("Initialized");
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Patch the sound device to open for sound output. Allows us to use a
|
||||
* different device than hw:0 (e.g. HDMI sound output)
|
||||
*/
|
||||
#ifndef PATCH_SOUND_H
|
||||
#define PATCH_SOUND_H
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*
|
||||
* @param dev_name Sound device name to open instead of whatever pump decides
|
||||
*/
|
||||
void patch_sound_init(const char* dev_name);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
#define LOG_MODULE "patch-usb-emu"
|
||||
|
||||
#include "capnhook/hooklib/usb-emu.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
void patch_usb_emu_init()
|
||||
{
|
||||
cnh_usbhook_push_handler(&cnh_usb_emu);
|
||||
|
||||
log_info("Initialized");
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Initialize the hook module for usb device emulation.
|
||||
*/
|
||||
#ifndef PATCH_USB_EMU_H
|
||||
#define PATCH_USB_EMU_H
|
||||
|
||||
/**
|
||||
* Initialize module.
|
||||
*/
|
||||
void patch_usb_emu_init();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
#define LOG_MODULE "patch-usb-init-fix"
|
||||
|
||||
#include "capnhook/hooklib/usb-init-fix.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
void patch_usb_init_fix_init()
|
||||
{
|
||||
cnh_usbhook_push_handler(&cnh_usb_init_fix);
|
||||
|
||||
log_info("Initialized");
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Initialize the hook module for fixing multiple usb_init calls.
|
||||
*/
|
||||
#ifndef PATCH_USB_INIT_FIX_H
|
||||
#define PATCH_USB_INIT_FIX_H
|
||||
|
||||
/**
|
||||
* Initialize module.
|
||||
*/
|
||||
void patch_usb_init_fix_init();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
#define LOG_MODULE "patch-usb-mnt"
|
||||
|
||||
#include "util/fs.h"
|
||||
#include "util/log.h"
|
||||
|
||||
#include "usb-mnt.h"
|
||||
|
||||
static const char* patch_usb_mnt_player[2] = {"/mnt/0", "/mnt/1"};
|
||||
// Invalid files are used to detect if mounting the usb stick was successful
|
||||
static const char* patch_usb_mnt_player_invalid[2] = {"/mnt/0/invalid", "/mnt/1/invalid"};
|
||||
|
||||
void patch_usb_mnt_init(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
if (!util_fs_path_exists(patch_usb_mnt_player[i])) {
|
||||
log_info("%s does not exist, creating", patch_usb_mnt_player[i]);
|
||||
|
||||
if (!util_fs_mkdir(patch_usb_mnt_player[i])) {
|
||||
log_error("Creating directory %s failed", patch_usb_mnt_player[i]);
|
||||
}
|
||||
|
||||
if (!util_fs_mkfile(patch_usb_mnt_player_invalid[i])) {
|
||||
log_error("Creating invalid indicator file %s failed", patch_usb_mnt_player_invalid[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log_info("Initialized");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Ensures that the mount points /mnt/0 and /mnt/1 exist to allow mounting
|
||||
* of connected usb sticks with profile data
|
||||
*/
|
||||
#ifndef PATCH_USB_MNT_H
|
||||
#define PATCH_USB_MNT_H
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*/
|
||||
void patch_usb_mnt_init(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,73 @@
|
||||
#define LOG_MODULE "patch-usb-unlock"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "capnhook/hook/lib.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#include "usb-unlock.h"
|
||||
|
||||
#define SERIAL_UNLOCK_TOKEN "PUMPToken\0"
|
||||
|
||||
typedef int (*sscanf_t)(const char *str, const char *format, ...);
|
||||
typedef int (*strcasecmp_t)(const char *s1, const char *s2);
|
||||
|
||||
static sscanf_t patch_usb_unlock_real_sscanf;
|
||||
static strcasecmp_t patch_usb_unlock_real_strcasecmp;
|
||||
|
||||
int sscanf(const char *str, const char *format, ...)
|
||||
{
|
||||
if (!patch_usb_unlock_real_sscanf) {
|
||||
patch_usb_unlock_real_sscanf = (sscanf_t) cnh_lib_get_func_addr("sscanf");
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
|
||||
if (!strcmp(format, "P: Vendor=%4x ProdID=%4x")) {
|
||||
log_debug("Spoofing VID 0x058f for usb stick");
|
||||
|
||||
/* unpack argument list, we know we got 2 arguments here */
|
||||
/* VID */
|
||||
*va_arg(ap, int*) = 0x058f;
|
||||
/* PID */
|
||||
*va_arg(ap, int*) = 0;
|
||||
|
||||
va_end(ap);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (!strcmp(format, "S: SerialNumber=%s")) {
|
||||
log_debug("Spoofing serial number of usb stick to kill serial number locking of profile in a moment...");
|
||||
memcpy(va_arg(ap, char*), SERIAL_UNLOCK_TOKEN, strlen(SERIAL_UNLOCK_TOKEN) + 1);
|
||||
|
||||
va_end(ap);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ret = vsscanf(str, format, ap);
|
||||
va_end(ap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int strcasecmp(const char *s1, const char *s2)
|
||||
{
|
||||
if (!patch_usb_unlock_real_strcasecmp) {
|
||||
patch_usb_unlock_real_strcasecmp = (strcasecmp_t) cnh_lib_get_func_addr("strcasecmp");
|
||||
}
|
||||
|
||||
if (!strcmp(s2, SERIAL_UNLOCK_TOKEN)) {
|
||||
log_debug("Caught serial unlock token...unlocking profile");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return patch_usb_unlock_real_strcasecmp(s1, s2);
|
||||
}
|
||||
|
||||
void patch_usb_unlock_init(void)
|
||||
{
|
||||
log_info("Initialized");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Remove the vendor id lock check for usb sticks limiting nx2 and nxa to
|
||||
* a specific brand of usb sticks to save profile data to
|
||||
*/
|
||||
#ifndef PATCH_USB_UNLOCK_H
|
||||
#define PATCH_USB_UNLOCK_H
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*/
|
||||
void patch_usb_unlock_init(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,130 @@
|
||||
#define LOG_MODULE "patch-usbfs"
|
||||
|
||||
#include <glob.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "capnhook/hook/lib.h"
|
||||
|
||||
#include "util/fs.h"
|
||||
#include "util/log.h"
|
||||
|
||||
typedef int (*vsprintf_t)(char* str, const char* format, va_list ap);
|
||||
typedef int (*sprintf_t)(char *str, const char *format, ...);
|
||||
|
||||
static vsprintf_t patch_usbfs_vsprintf_orig;
|
||||
static sprintf_t patch_usbfs_sprintf_orig;
|
||||
|
||||
static const char* patch_usbfs_usb_str = "/sys/bus/usb/devices/";
|
||||
|
||||
int sprintf(char *str, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
int ret = vsprintf(str, format, ap);
|
||||
va_end(ap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int __sprintf_chk(char *dest, int flags, size_t dest_len_from_compiler, const char *format, ...)
|
||||
{
|
||||
int ret;
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
|
||||
/* fuck...why did you guys have to switch from sprintf to sprintf_chk?
|
||||
everything was perfectly fine... and you give a shit about "safe" code,
|
||||
which doesn't make this mess better... */
|
||||
ret = vsprintf(dest, format, ap);
|
||||
|
||||
va_end(ap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int vsprintf(char* str, const char* format, va_list ap)
|
||||
{
|
||||
if (!patch_usbfs_vsprintf_orig) {
|
||||
patch_usbfs_vsprintf_orig = (vsprintf_t) cnh_lib_get_func_addr("vsprintf");
|
||||
}
|
||||
|
||||
if (!patch_usbfs_sprintf_orig) {
|
||||
patch_usbfs_sprintf_orig = (sprintf_t) cnh_lib_get_func_addr("sprintf");
|
||||
}
|
||||
|
||||
/* This path does not work on newer kernels (not sure which version it
|
||||
started but everything > 3.18 doesn't work) */
|
||||
|
||||
// nx2/nxa path:
|
||||
// /sys/bus/usb/devices/%d-%d/%d-%d:1.0/host*/target*:0:0/*:0:0:0/block:sd*
|
||||
// fiesta and newer:
|
||||
// /sys/bus/usb/devices/%d-%d:1.0/host*/target*:0:0/*:0:0:0/block/sd*
|
||||
|
||||
if (!strncmp(patch_usbfs_usb_str, format, strlen(patch_usbfs_usb_str))) {
|
||||
/* unpack argument list, we know we got 4 arguments here
|
||||
the second pair is a dupe of the first one */
|
||||
uint8_t bus = (uint8_t) va_arg(ap, int);
|
||||
uint8_t port = (uint8_t) va_arg(ap, int);
|
||||
|
||||
// Removed because it spams the log. Put it back when debugging usb profile mounting related issues
|
||||
// log_debug("Patching lookup of '%s' device node for bus %d, port %d.", format, bus, port);
|
||||
|
||||
/* note: ports start with 1 (not 0) in sysfs */
|
||||
|
||||
/* because the linux kernel likes to change things here often, we have to consider different paths.
|
||||
for each path we encountered so far (add more if missing from different kernel versions) just
|
||||
check if the path exists and use it */
|
||||
|
||||
int counter = 0;
|
||||
|
||||
while (counter <= 2) {
|
||||
switch (counter) {
|
||||
case 0:
|
||||
patch_usbfs_sprintf_orig(str, "/sys/bus/usb/devices/%d-1.%d:1.0/host*/"
|
||||
"target*:0:0/*:0:0:0/block/sd*", bus, port);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
patch_usbfs_sprintf_orig(str, "/sys/bus/usb/devices/%d-%d:1.0/host*/"
|
||||
"target*:0:0/*:0:0:0/block/sd*", bus, port);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Discovered that the string needs to be changed on 5.5.6. Probably changed slightly before that
|
||||
// version
|
||||
patch_usbfs_sprintf_orig(str, "/sys/bus/usb/devices/%d-%d/%d-%d:1.0/host*/"
|
||||
"target*:0:0/*:0:0:0/block/sd*", bus, port, bus, port);
|
||||
break;
|
||||
|
||||
default:
|
||||
log_error("Could not find path in sys-fs to lookup usb drive block device. Likely, you have a "
|
||||
"kernel version that uses a (slightly) different path. This (new) path must be added in "
|
||||
"order to get usb drives showing up in the game.");
|
||||
break;
|
||||
}
|
||||
|
||||
glob_t gl;
|
||||
|
||||
// Pump does the same after returning from this hooked call. Because there are different file system
|
||||
// layouts depending on the kernel version, try the above list using the same method with glob
|
||||
if (!glob(str, 4, NULL, &gl)) {
|
||||
globfree(&gl);
|
||||
break;
|
||||
}
|
||||
|
||||
globfree(&gl);
|
||||
|
||||
counter++;
|
||||
}
|
||||
|
||||
/* original argument count */
|
||||
return 4;
|
||||
}
|
||||
|
||||
return patch_usbfs_vsprintf_orig(str, format, ap);
|
||||
}
|
||||
|
||||
void patch_usbfs_init()
|
||||
{
|
||||
log_info("Initialized");
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* The kernels used by nx2 and nxa support usbfs. Newer kernels
|
||||
* don't have this module anymore. This module takes care of fixing any
|
||||
* issues not having the module.
|
||||
*/
|
||||
#ifndef PATCH_USBFS_H
|
||||
#define PATCH_USBFS_H
|
||||
|
||||
/**
|
||||
* Initialize the patch module
|
||||
*/
|
||||
void patch_usbfs_init();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,269 @@
|
||||
#define LOG_MODULE "patch-x11-event-loop"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdbool.h>
|
||||
#include <X11/Xutil.h>
|
||||
|
||||
#include "capnhook/hook/lib.h"
|
||||
|
||||
#include "ptapi/io/x11-input-hook.h"
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/mem.h"
|
||||
#include "util/time.h"
|
||||
|
||||
struct patch_x11_event_loop_ctx {
|
||||
Display* display;
|
||||
Window window;
|
||||
pthread_t thread;
|
||||
atomic_int run;
|
||||
};
|
||||
|
||||
typedef Window (*XCreateWindow_t)(Display *display, Window parent, int x, int y, unsigned int width,
|
||||
unsigned int height, unsigned int border_width, int depth, unsigned int _class, Visual *visual,
|
||||
unsigned long valuemask, XSetWindowAttributes *attributes);
|
||||
typedef Display* (*XOpenDisplay_t)(const char *display_name);
|
||||
typedef int (*XDestroyWindow_t)(Display *display, Window w);
|
||||
|
||||
static XCreateWindow_t patch_x11_event_loop_real_XCreateWindow;
|
||||
static XOpenDisplay_t patch_x11_event_loop_real_XOpenDisplay;
|
||||
static XDestroyWindow_t patch_x11_event_loop_real_XDestroyWindow;
|
||||
|
||||
static bool patch_x11_event_loop_enabled;
|
||||
static struct patch_x11_event_loop_ctx* patch_x11_event_loop_ctx;
|
||||
|
||||
static pthread_mutex_t patch_x11_event_loop_input_handlers_lock;
|
||||
static const struct ptapi_io_x11_input_hook_handler** patch_x11_event_loop_input_handlers;
|
||||
static size_t patch_x11_event_loop_num_input_handlers;
|
||||
|
||||
static bool patch_x11_event_loop_is_key_repeated(Display* display, XEvent* event)
|
||||
{
|
||||
/* When a key is repeated, there will be two events: released, followed by another immediate pressed.
|
||||
So check to see if another pressed is present */
|
||||
if (!XPending(display)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XEvent e;
|
||||
XPeekEvent(display, &e);
|
||||
|
||||
if (e.type == KeyPress && e.xkey.keycode == event->xkey.keycode && (e.xkey.time - event->xkey.time) < 2) {
|
||||
XNextEvent(display, &e);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void* patch_x11_event_loop_run(void* args)
|
||||
{
|
||||
log_info("Running X11 event loop thread");
|
||||
|
||||
struct patch_x11_event_loop_ctx* ctx = (struct patch_x11_event_loop_ctx*) args;
|
||||
|
||||
if (XSelectInput(ctx->display, ctx->window, KeyPressMask | KeyReleaseMask) == BadWindow) {
|
||||
log_error("XSelectInput failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
XGrabKeyboard(ctx->display, ctx->window, 1, GrabModeAsync, GrabModeAsync, CurrentTime);
|
||||
|
||||
while (atomic_load(&ctx->run) > 0) {
|
||||
while (atomic_load(&ctx->run) > 0 && XPending(ctx->display) > 0) {
|
||||
XEvent event;
|
||||
KeySym key;
|
||||
|
||||
if (XNextEvent(ctx->display, &event) != Success) {
|
||||
log_error("XNextEvent");
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case KeyPress:
|
||||
{
|
||||
/* Mask out the modifier states X11 sets and read again */
|
||||
event.xkey.state &= ~ShiftMask;
|
||||
event.xkey.state &= ~LockMask;
|
||||
|
||||
XLookupString(&event.xkey, 0, 0, &key, 0);
|
||||
|
||||
/* Dispatch to external input handlers */
|
||||
if (patch_x11_event_loop_num_input_handlers > 0) {
|
||||
pthread_mutex_lock(&patch_x11_event_loop_input_handlers_lock);
|
||||
|
||||
for (size_t i = 0; i < patch_x11_event_loop_num_input_handlers; i++) {
|
||||
if (patch_x11_event_loop_input_handlers[i]->dispatch_key_press) {
|
||||
patch_x11_event_loop_input_handlers[i]->dispatch_key_press(key);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&patch_x11_event_loop_input_handlers_lock);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case KeyRelease:
|
||||
{
|
||||
/* Mask out the modifier states X11 sets and read again */
|
||||
event.xkey.state &= ~ShiftMask;
|
||||
event.xkey.state &= ~LockMask;
|
||||
|
||||
XLookupString(&event.xkey, 0, 0, &key, 0);
|
||||
|
||||
/* Dispatch to external input handlers */
|
||||
if (patch_x11_event_loop_num_input_handlers > 0) {
|
||||
/* Dispatch release if key is not repeated */
|
||||
if (!patch_x11_event_loop_is_key_repeated(ctx->display, &event)) {
|
||||
pthread_mutex_lock(&patch_x11_event_loop_input_handlers_lock);
|
||||
|
||||
for (size_t i = 0; i < patch_x11_event_loop_num_input_handlers; i++) {
|
||||
if (patch_x11_event_loop_input_handlers[i]->dispatch_key_release) {
|
||||
patch_x11_event_loop_input_handlers[i]->dispatch_key_release(key);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&patch_x11_event_loop_input_handlers_lock);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid CPU banging
|
||||
util_time_sleep_ms(1);
|
||||
}
|
||||
|
||||
log_info("Finished X11 event loop thread");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Display *XOpenDisplay(const char *display_name)
|
||||
{
|
||||
if (!patch_x11_event_loop_real_XOpenDisplay) {
|
||||
patch_x11_event_loop_real_XOpenDisplay = (XOpenDisplay_t) cnh_lib_get_func_addr("XOpenDisplay");
|
||||
}
|
||||
|
||||
log_debug("XOpenDisplay: %s", display_name);
|
||||
|
||||
// This function needs to be called before any other X11 function is called.
|
||||
// Required to pump events in separate thread
|
||||
if (patch_x11_event_loop_enabled && XInitThreads() != Success) {
|
||||
log_error("XInitThreads failed");
|
||||
}
|
||||
|
||||
Display* res = patch_x11_event_loop_real_XOpenDisplay(display_name);
|
||||
|
||||
if (!res) {
|
||||
log_error("XOpenDisplay returned NULL. This might indicate that your environment is not properly setup. Check "
|
||||
" that you have GPU drivers installed and configured properly and that OpenGL hardware acceleration is "
|
||||
" working (use the command line tool \"glxinfo\").");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Window XCreateWindow(Display *display, Window parent, int x, int y, unsigned int width, unsigned int height,
|
||||
unsigned int border_width, int depth, unsigned int _class, Visual *visual, unsigned long valuemask,
|
||||
XSetWindowAttributes *attributes)
|
||||
{
|
||||
log_debug("XCreateWindow");
|
||||
|
||||
if (!patch_x11_event_loop_real_XCreateWindow) {
|
||||
patch_x11_event_loop_real_XCreateWindow = (XCreateWindow_t) cnh_lib_get_func_addr("XCreateWindow");
|
||||
}
|
||||
|
||||
Window w = patch_x11_event_loop_real_XCreateWindow(display, parent, x, y, width, height, border_width, depth,
|
||||
_class, visual, valuemask, attributes);
|
||||
|
||||
if (patch_x11_event_loop_enabled && !patch_x11_event_loop_ctx) {
|
||||
patch_x11_event_loop_ctx = util_xmalloc(sizeof(struct patch_x11_event_loop_ctx));
|
||||
|
||||
patch_x11_event_loop_ctx->display = display;
|
||||
patch_x11_event_loop_ctx->window = w;
|
||||
patch_x11_event_loop_ctx->run = ATOMIC_VAR_INIT(1);
|
||||
|
||||
pthread_create(&patch_x11_event_loop_ctx->thread, NULL, patch_x11_event_loop_run, patch_x11_event_loop_ctx);
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
int XDestroyWindow(Display *display, Window w)
|
||||
{
|
||||
log_debug("XDestroyWindow");
|
||||
|
||||
if (!patch_x11_event_loop_real_XDestroyWindow) {
|
||||
patch_x11_event_loop_real_XDestroyWindow = (XDestroyWindow_t) cnh_lib_get_func_addr("XDestroyWindow");
|
||||
}
|
||||
|
||||
if (patch_x11_event_loop_enabled && patch_x11_event_loop_ctx) {
|
||||
log_debug("Stopping X11 event loop thread...");
|
||||
|
||||
atomic_store(&patch_x11_event_loop_ctx->run, 0);
|
||||
pthread_join(patch_x11_event_loop_ctx->thread, NULL);
|
||||
free(patch_x11_event_loop_ctx);
|
||||
patch_x11_event_loop_ctx = NULL;
|
||||
|
||||
log_debug("Stopped X11 event loop thread");
|
||||
}
|
||||
|
||||
return patch_x11_event_loop_real_XDestroyWindow(display, w);
|
||||
}
|
||||
|
||||
void patch_x11_event_loop_init()
|
||||
{
|
||||
patch_x11_event_loop_enabled = true;
|
||||
log_info("Initialized");
|
||||
}
|
||||
|
||||
void patch_x11_event_loop_add_input_handler(const char* lib_with_handler_impl)
|
||||
{
|
||||
const struct ptapi_io_x11_input_hook_handler** new_array;
|
||||
size_t new_size;
|
||||
void* lib_handle;
|
||||
ptapi_io_x11_input_handler_hook_t input_handler_hook_func;
|
||||
const struct ptapi_io_x11_input_hook_handler* input_handler_hook;
|
||||
|
||||
if (!lib_with_handler_impl) {
|
||||
log_die("No library path with input handler implementation specified");
|
||||
}
|
||||
|
||||
log_info("Loading library with x11-input-handler api implementation %s", lib_with_handler_impl);
|
||||
|
||||
lib_handle = cnh_lib_load(lib_with_handler_impl);
|
||||
|
||||
input_handler_hook_func = (ptapi_io_x11_input_handler_hook_t)
|
||||
cnh_lib_get_func_addr_handle(lib_handle, "ptapi_io_x11_input_handler_hook");
|
||||
|
||||
input_handler_hook = input_handler_hook_func();
|
||||
|
||||
if (!input_handler_hook) {
|
||||
log_warn("Library impl %s returned NULL hook handler, skipping", lib_with_handler_impl);
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&patch_x11_event_loop_input_handlers_lock);
|
||||
|
||||
new_size = patch_x11_event_loop_num_input_handlers + 1;
|
||||
new_array = realloc(patch_x11_event_loop_input_handlers, new_size * sizeof(struct ptapi_io_input_hook_handler*));
|
||||
|
||||
if (new_array != NULL) {
|
||||
patch_x11_event_loop_input_handlers = new_array;
|
||||
|
||||
patch_x11_event_loop_input_handlers[patch_x11_event_loop_num_input_handlers++] = input_handler_hook;
|
||||
} else {
|
||||
log_die("Out of memory");
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&patch_x11_event_loop_input_handlers_lock);
|
||||
|
||||
log_debug("Added input handler %p of lib %s", input_handler_hook, lib_with_handler_impl);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef PROHOOK_X11_EVENT_LOOP_H
|
||||
#define PROHOOK_X11_EVENT_LOOP_H
|
||||
|
||||
/**
|
||||
* Initialize this module which re-implements a X11 event loop processing keyboard button presses and releases.
|
||||
* This is required for Pro 1/2 because the game has all X11 input handling removed. Therefore, we cannot use any
|
||||
* ptapi io implementations using X11, e.g. keyboard inputs.
|
||||
*/
|
||||
void patch_x11_event_loop_init();
|
||||
|
||||
/**
|
||||
* Register a handler (multiple possible) to receive X11 input events, e.g. key pressed, released
|
||||
*
|
||||
* @param lib_with_handler_impl Path to a library implementing the x11-input-handler API
|
||||
*/
|
||||
void patch_x11_event_loop_add_input_handler(const char* lib_with_handler_impl);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user