Version 1.08 with public source code release.

This commit is contained in:
icex2
2020-10-03 20:56:55 +02:00
parent 656148121d
commit 762bf140c0
555 changed files with 55502 additions and 3 deletions
+53
View File
@@ -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");
}
+13
View File
@@ -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
+147
View File
@@ -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");
}
+25
View File
@@ -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
+66
View File
@@ -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);
}
+19
View File
@@ -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
+36
View File
@@ -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");
}
+10
View File
@@ -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
+125
View File
@@ -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;
}
+17
View File
@@ -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
+63
View File
@@ -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");
}
+15
View File
@@ -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
+40
View File
@@ -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);
}
+22
View File
@@ -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
+257
View File
@@ -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);
}
+16
View File
@@ -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
+36
View File
@@ -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");
}
+20
View File
@@ -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
+235
View File
@@ -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;
}
+59
View File
@@ -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
+72
View File
@@ -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;
}
}
+15
View File
@@ -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
+40
View File
@@ -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);
}
+20
View File
@@ -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
+105
View File
@@ -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");
}
+13
View File
@@ -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
+594
View File
@@ -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");
}
+31
View File
@@ -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
+96
View File
@@ -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);
}
}
+23
View File
@@ -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
+292
View File
@@ -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;
}
+32
View File
@@ -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
+87
View File
@@ -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;
}
+12
View File
@@ -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
+572
View File
@@ -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;
}
+22
View File
@@ -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
+25
View File
@@ -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);
}
}
+13
View File
@@ -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
+14
View File
@@ -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");
}
+12
View File
@@ -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
+53
View File
@@ -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");
}
+16
View File
@@ -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
+66
View File
@@ -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");
}
+15
View File
@@ -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
+12
View File
@@ -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");
}
+12
View File
@@ -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
+12
View File
@@ -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");
}
+12
View File
@@ -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
+29
View File
@@ -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");
}
+13
View File
@@ -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
+73
View File
@@ -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");
}
+13
View File
@@ -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
+130
View File
@@ -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");
}
+14
View File
@@ -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
+269
View File
@@ -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);
}
+18
View File
@@ -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