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
+50
View File
@@ -0,0 +1,50 @@
#include "hook/core/options.h"
#include "util/options.h"
#define HOOK_CORE_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file"
#define HOOK_CORE_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level"
const struct util_options_def hook_core_options_def[] = {
{
.name = HOOK_CORE_OPTIONS_STR_PATCH_UTIL_LOG_FILE,
.description = "Print the log output to the specified file",
.param = 'o',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "pumptools.log",
},
{
.name = HOOK_CORE_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL,
.description = "Set the log level (0-4)",
.param = 'l',
.type = UTIL_OPTIONS_TYPE_INT,
.default_value.i = LOG_LEVEL_DEBUG,
},
};
const struct util_options_defs hook_core_options_defs = {
.usage_header = "",
// Just use something because this is not relevant/required here
.usage_param = '\r',
.defs = hook_core_options_def,
.ndefs = lengthof(hook_core_options_def)
};
void hook_core_options_init(int argc, char** argv, struct hook_core_options* options)
{
log_assert(argv);
log_assert(options);
struct util_options_opts* options_opt;
util_options_init(argc, argv);
options_opt = util_options_get(&hook_core_options_defs);
if (!options_opt) {
return;
}
options->log.file = util_options_get_str(options_opt, HOOK_CORE_OPTIONS_STR_PATCH_UTIL_LOG_FILE);
options->log.level =
(enum util_log_level) util_options_get_int(options_opt, HOOK_CORE_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL);
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef HOOK_CORE_OPTIONS_H
#define HOOK_CORE_OPTIONS_H
#include "util/log.h"
struct hook_core_options {
struct log {
const char* file;
enum util_log_level level;
} log;
};
void hook_core_options_init(int argc, char** argv, struct hook_core_options* options);
#endif
+133
View File
@@ -0,0 +1,133 @@
#define LOG_MODULE "hook-core-piu-utils"
#include <locale.h>
#include <stdlib.h>
#include <string.h>
#include "hook/core/options.h"
#include "util/fs.h"
#include "util/glibc.h"
#include "util/log.h"
#include "util/proc.h"
#include "util/str.h"
#include "util/sys-info.h"
static void hook_core_piu_log_application_args(int argc, char** argv)
{
const char* sep = "\n";
char* str = util_str_dup("");
char* tmp;
for (int i = 0; i < argc; i++) {
if (strlen(str) > 0) {
tmp = util_str_merge(str, sep);
free(str);
str = tmp;
}
tmp = util_str_merge(str, argv[i]);
free(str);
str = tmp;
}
log_info("Application arguments (%d):\n%s", argc, str);
free(str);
}
void hook_core_piu_log_init(int argc, char** argv)
{
struct hook_core_options options;
hook_core_options_init(argc, argv, &options);
util_log_set_file(options.log.file, false);
util_log_set_level(options.log.level);
}
void hook_core_piu_log_info(int argc, char** argv)
{
log_info("\n"
" _ _ \n"
" _ __ _ _ _ __ ___ _ __ | |_ ___ ___ | |___ \n"
" | '_ \\| | | | '_ ` _ \\| '_ \\| __/ _ \\ / _ \\| / __|\n"
" | |_) | |_| | | | | | | |_) | || (_) | (_) | \\__ \\\n"
" | .__/ \\__,_|_| |_| |_| .__/ \\__\\___/ \\___/|_|___/\n"
" |_| |_| ");
log_info("Pumptools build " __DATE__ " " __TIME__ " gitrev %s", STRINGIFY(GITREV));
util_sys_info_log();
util_proc_log_info();
util_glibc_info_log();
hook_core_piu_log_application_args(argc, argv);
}
char* hook_core_piu_utils_get_and_verify_game_data_path(int argc, char** argv)
{
if (argc < 2) {
log_error("Missing game data path, usage for piu (stock args): piu <game folder> (e.g. piu ./game)");
exit(0);
}
char* game_data_path = util_str_dup(argv[1]);
log_info("Game data path: %s", game_data_path);
/* remove trailing / for the hook. however, the game crashes without it */
if (util_str_ends_with(game_data_path, "/")) {
game_data_path[strlen(game_data_path) - 1] = '\0';
} else {
log_error("Game data path '%s' does not end with a trailing /, exiting because the game is going to "
"crash anyway!", game_data_path);
exit(0);
}
/* depending on the version, the game also just crashes if the game folder or files within it were not found */
if (!util_fs_path_exists(game_data_path)) {
log_error("Game data path '%s' does not exist, exiting", game_data_path);
exit(0);
}
return game_data_path;
}
void hook_core_piu_utils_verify_root_user()
{
log_info("Verifying process is running as root user.");
if (!util_proc_is_running_as_root()) {
log_error("Process not running as root which is required for the game to work correctly.");
//exit(0);
}
}
void hook_core_piu_utils_fix_locale()
{
log_info("Fixing locale settings.");
if (setenv("LC_ALL", "C", 1) != 0) {
log_warn("Setting locale LC_ALL failed.");
}
if (unsetenv("LANGUAGE") != 0) {
log_warn("Unsetting LANGUAGE env var failed.");
}
}
bool hook_core_piu_block_non_piu_process_recursion(int argc, char** argv)
{
/* Sanity check for arguments. If further child processes are started
from the current process, and using LD_PRELOAD on the current process,
all children are also getting the preloaded library. This causes
multiple calls to the trap main function and just crashes our
application at some point.
The executable file is required to contain the "piu" substring to make
this work properly */
if (!strstr(argv[0], "piu")) {
log_warn("Not attaching to non piu process %s", argv[0]);
return true;
} else {
return false;
}
}
+53
View File
@@ -0,0 +1,53 @@
#ifndef HOOK_CORE_PIU_UTILS_H
#define HOOK_CORE_PIU_UTILS_H
/**
* Initialize the logging system as early as possible to allow recording everything to a log file as well.
*/
void hook_core_piu_log_init(int argc, char** argv);
/**
* Log process and system relevant information.
*/
void hook_core_piu_log_info(int argc, char** argv);
/**
* Check and verify the game data path argument passed to the game via argv
*
* Executes various checks and catches errors that might result in the game
* simply crashing without any error indication.
*
* @param argc Argc from piu main
* @param argv Argv from piu main
* @return New string with game data path extracted from argv (caller has to free)
*/
char* hook_core_piu_utils_get_and_verify_game_data_path(int argc, char** argv);
/**
* Verify that the current process is running as root user because the game
* needs access to various devices that require higher privileges.
*
* Does not return and errors if not running as root user.
*/
void hook_core_piu_utils_verify_root_user();
/**
* Fix the locale configuration for Fiesta 2 and newer.
*/
void hook_core_piu_utils_fix_locale();
/**
* If further child processes are started from the current process, and using
* LD_PRELOAD on the current process, all children are also getting the preloaded
* library. This causes multiple calls to the trap main function and just crashes our
* application at some point. The executable file is required to contain the "piu"
* substring to make this work properly.
*
* @param argc Argc from piu main
* @param argv Argv from piu main
* @return True if we have to block this call by another process,
* false if the process calling this is the piu process.
*/
bool hook_core_piu_block_non_piu_process_recursion(int argc, char** argv);
#endif
+98
View File
@@ -0,0 +1,98 @@
#define LOG_MODULE "exchook-eeprom"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "hook/exc/mempatch.h"
#include "util/fs.h"
#include "util/log.h"
#include "util/patch.h"
static int32_t exchook_eeprom_read(uint8_t* buffer, int32_t len);
static int32_t exchook_eeprom_write(const uint8_t* buffer, int32_t len);
static char* exchook_eeprom_file;
static uint8_t exchook_eeprom_buffer[8192];
void exchook_eeprom_init(const struct exchook_mempatch_table* patch_table, const char* eeprom_file)
{
void* buffer;
size_t len;
exchook_eeprom_file = util_fs_get_abs_path(eeprom_file);
/* EEPROM file does not exist, create new */
if (!exchook_eeprom_file) {
memset(exchook_eeprom_buffer, 0xFF, sizeof(exchook_eeprom_buffer));
FILE* file = fopen(eeprom_file, "wb+");
if (file == NULL) {
log_error("Creating new eeprom file %s failed: %s", eeprom_file, strerror(errno));
}
fwrite(exchook_eeprom_buffer, sizeof(exchook_eeprom_buffer), 1, file);
fclose(file);
exchook_eeprom_file = util_fs_get_abs_path(eeprom_file);
len = (size_t) -1;
} else {
if (util_file_load(exchook_eeprom_file, &buffer, &len, false)) {
if (len > sizeof(exchook_eeprom_buffer)) {
log_error("Contents of %s eeprom file exceed the max eeprom buffer "
" size of %d bytes", eeprom_file,
sizeof(exchook_eeprom_buffer));
free(buffer);
return;
}
/* copy read contents */
memcpy(exchook_eeprom_buffer, buffer, len);
free(buffer);
} else {
log_error("Loading eeprom file %s failed", exchook_eeprom_file);
}
}
log_assert(exchook_eeprom_file);
/* Hook eeprom */
util_patch_function(patch_table->addr_eeprom_read, exchook_eeprom_read);
util_patch_function(patch_table->addr_eeprom_write, exchook_eeprom_write);
log_info("Initizlied (%s), eeprom file %s", len == -1 ? "new" : "loaded", eeprom_file);
}
static int32_t exchook_eeprom_read(uint8_t* buffer, int32_t len)
{
if (len > sizeof(exchook_eeprom_buffer)) {
log_error("Eeprom read %d exceeds buffer size %d", len,
sizeof(exchook_eeprom_buffer));
return 0;
}
memcpy(buffer, exchook_eeprom_buffer, len);
return len;
}
static int32_t exchook_eeprom_write(const uint8_t* buffer, int32_t len)
{
if (len > sizeof(exchook_eeprom_buffer)) {
log_error("Eeprom write %d exceeds buffer size %d", len,
sizeof(exchook_eeprom_buffer));
return 0;
}
memcpy(exchook_eeprom_buffer, buffer, len);
/* Write back to file */
if (!util_file_save(exchook_eeprom_file, exchook_eeprom_buffer, len)) {
log_error("Storing eeprom contents to file %s failed",
exchook_eeprom_file);
}
return len;
}
+19
View File
@@ -0,0 +1,19 @@
/**
* Patch module emulating the MK5 (hardware) eeprom chip used to store
* game settings to on Exceed
*/
#ifndef EXCHOOK_EEPROM_H
#define EXCHOOK_EEPROM_H
#include "hook/exc/mempatch.h"
/**
* Init eeprom emulation for exceed
*
* @param patch_table Patch table with memory addresses
* @param eeprom_file Path to existing eeprom file to read from and new eeprom
* file to store to
*/
void exchook_eeprom_init(const struct exchook_mempatch_table* patch_table, const char* eeprom_file);
#endif
+505
View File
@@ -0,0 +1,505 @@
#define LOG_MODULE "exchook-io"
#include <stdlib.h>
#include "ptapi/io/piuio.h"
#include "ptapi/io/piuio/util/lib.h"
#include "hook/exc/mempatch.h"
#include "util/log.h"
#include "util/patch.h"
/* Taken from ioarcade.hpp nx2 src dump */
#define GI_P1_1 0x00000001
#define GI_P1_3 0x00000002
#define GI_P1_5 0x00000004
#define GI_P1_7 0x00000008
#define GI_P1_9 0x00000010
#define GI_P2_1 0x00000100
#define GI_P2_3 0x00000200
#define GI_P2_5 0x00000400
#define GI_P2_7 0x00000800
#define GI_P2_9 0x00001000
#define GI_TEST 0x00010000
#define GI_SERVICE 0x00020000
#define GI_CLEAR 0x00040000
#define GI_COIN 0x00100000
#define GI_COIN2 0x00200000
struct exchook_io_sensores {
uint32_t lu : 4;
uint32_t ru : 4;
uint32_t cn : 4;
uint32_t ld : 4;
uint32_t rd : 4;
uint32_t padding : 12;
};
static const uint8_t exchook_io_sensor_mappings[] = {
1, 2, 3, 0
};
static struct ptapi_io_piuio_api exchook_piuio_api;
static uint16_t exchook_io_check(void* obj);
static int32_t exchook_io_light_all(void* obj, uint8_t on);
static int32_t exchook_io_light_neon(void* obj, uint8_t on);
static int32_t exchook_io_light_halogan(void* obj, uint8_t on, uint16_t light_id);
static int32_t exchook_io_light_p1_ccfl(void* obj, uint8_t on, uint16_t cmd);
static int32_t exchook_io_light_p2_ccfl(void* obj, uint8_t on, uint16_t cmd);
static int32_t exchook_io_input(void* obj);
static int32_t exchook_io_coin_input(void* obj);
static int32_t exchook_io_coin_input_2(void* obj);
static int32_t exchook_io_coin_counter_2(void* obj);
static uint32_t* exchook_io_game_input_stat;
static uint32_t* exchook_io_game_input_down;
static uint32_t* exchook_io_game_input_up;
static uint32_t exchook_io_game_input_stat_prev;
static struct exchook_io_sensores exchook_io_sensores_p1;
static struct exchook_io_sensores exchook_io_sensores_p2;
static struct ptapi_io_piuio_pad_outputs exchook_io_out_pad_p1;
static struct ptapi_io_piuio_pad_outputs exchook_io_out_pad_p2;
static struct ptapi_io_piuio_pad_outputs exchook_io_out_pad_press_p1;
static struct ptapi_io_piuio_pad_outputs exchook_io_out_pad_press_p2;
static struct ptapi_io_piuio_cab_outputs exchook_io_out_cab;
static bool exchook_io_exit_on_service_test;
void exchook_io_init(const struct exchook_mempatch_table* patch_table, const char* piuio_lib_path,
bool exit_on_service_test)
{
if (!piuio_lib_path) {
log_error("NULL piuio (emu) lib path. Check that you have set-up and configured a piuio API implementation "
"correctly.");
return;
}
/* Load piuio library funcs */
log_info("Loading piuio api implementation %s", piuio_lib_path);
if (!ptapi_io_piuio_util_lib_load(piuio_lib_path, &exchook_piuio_api)) {
log_error("Loading piuio API from file %s failed", piuio_lib_path);
return;
}
exchook_io_exit_on_service_test = exit_on_service_test;
log_info("Service + Test exit %d", exchook_io_exit_on_service_test);
/* Hook MK5IO engine calls */
/* Why so many functions you might ask? Because the MK5 IO code is a pile
of shit created by a monkey */
util_patch_function(patch_table->addr_io_check, exchook_io_check);
util_patch_function(patch_table->addr_io_light_all, exchook_io_light_all);
util_patch_function(patch_table->addr_io_light_neon, exchook_io_light_neon);
util_patch_function(patch_table->addr_io_light_halogan, exchook_io_light_halogan);
util_patch_function(patch_table->addr_io_light_p1_ccfl, exchook_io_light_p1_ccfl);
util_patch_function(patch_table->addr_io_light_p2_ccfl, exchook_io_light_p2_ccfl);
util_patch_function(patch_table->addr_io_input, exchook_io_input);
util_patch_function(patch_table->addr_io_coin_input, exchook_io_coin_input);
util_patch_function(patch_table->addr_io_coin_input_2, exchook_io_coin_input_2);
util_patch_function(patch_table->addr_io_coin_counter_2, exchook_io_coin_counter_2);
exchook_io_game_input_stat = ((uint32_t*) patch_table->addr_io_game_input_stat);
exchook_io_game_input_down = ((uint32_t*) patch_table->addr_io_game_input_down);
exchook_io_game_input_up = ((uint32_t*) patch_table->addr_io_game_input_up);
/* there is an io init method but obviously requires another address to be
hooked...just init our backend here */
if (!exchook_piuio_api.open()) {
log_error("Opening io failed");
return;
}
log_info("Initialized");
}
void exchook_io_shutdown(void)
{
}
/* check? why can't you just call it get_fucking_inputs... */
static uint16_t exchook_io_check(void* obj)
{
struct ptapi_io_piuio_pad_inputs p1_pad_in;
struct ptapi_io_piuio_pad_inputs p2_pad_in;
/* Sensor states are expected to be returned using the object */
struct exchook_io_sensores* sensores_p1 =
(struct exchook_io_sensores*) obj;
struct exchook_io_sensores* sensores_p2 =
(struct exchook_io_sensores*) (((uint32_t*) obj) + 1);
/* execute polling here */
/* might delay output for a frame but whatever */
if (!exchook_piuio_api.send()) {
log_error("Piuio send failed");
}
if (!exchook_piuio_api.recv()) {
log_error("Piuio receive failed");
}
/* MK5 inputs state reset */
exchook_io_sensores_p1.lu = 0;
exchook_io_sensores_p1.ru = 0;
exchook_io_sensores_p1.cn = 0;
exchook_io_sensores_p1.ld = 0;
exchook_io_sensores_p1.rd = 0;
exchook_io_sensores_p2.lu = 0;
exchook_io_sensores_p2.ru = 0;
exchook_io_sensores_p2.cn = 0;
exchook_io_sensores_p2.ld = 0;
exchook_io_sensores_p2.rd = 0;
// State reset
memset(&p1_pad_in, 0, sizeof(struct ptapi_io_piuio_pad_inputs));
memset(&p2_pad_in, 0, sizeof(struct ptapi_io_piuio_pad_inputs));
/* cycle all sensores */
for (uint8_t i = 0; i < 4; i++) {
exchook_piuio_api.get_input_pad(0, i, &p1_pad_in);
exchook_piuio_api.get_input_pad(1, i, &p2_pad_in);
/* process inputs */
/* Player 1 */
if (p1_pad_in.lu) {
exchook_io_sensores_p1.lu |= (1 << exchook_io_sensor_mappings[i]);
}
if (p1_pad_in.ru) {
exchook_io_sensores_p1.ru |= (1 << exchook_io_sensor_mappings[i]);
}
if (p1_pad_in.cn) {
exchook_io_sensores_p1.cn |= (1 << exchook_io_sensor_mappings[i]);
}
if (p1_pad_in.ld) {
exchook_io_sensores_p1.ld |= (1 << exchook_io_sensor_mappings[i]);
}
if (p1_pad_in.rd) {
exchook_io_sensores_p1.rd |= (1 << exchook_io_sensor_mappings[i]);
}
/* Player 2 */
if (p2_pad_in.lu) {
exchook_io_sensores_p2.lu |= (1 << exchook_io_sensor_mappings[i]);
}
if (p2_pad_in.ru) {
exchook_io_sensores_p2.ru |= (1 << exchook_io_sensor_mappings[i]);
}
if (p2_pad_in.cn) {
exchook_io_sensores_p2.cn |= (1 << exchook_io_sensor_mappings[i]);
}
if (p2_pad_in.ld) {
exchook_io_sensores_p2.ld |= (1 << exchook_io_sensor_mappings[i]);
}
if (p2_pad_in.rd) {
exchook_io_sensores_p2.rd |= (1 << exchook_io_sensor_mappings[i]);
}
}
memcpy(sensores_p1, &exchook_io_sensores_p1, sizeof(exchook_io_sensores_p1));
memcpy(sensores_p2, &exchook_io_sensores_p2, sizeof(exchook_io_sensores_p2));
return 0;
}
static int32_t exchook_io_light_all(void* obj, uint8_t on)
{
if (on) {
memset(&exchook_io_out_pad_p1, 1, sizeof(exchook_io_out_pad_p1));
memset(&exchook_io_out_pad_p2, 1, sizeof(exchook_io_out_pad_p2));
memset(&exchook_io_out_cab, 1, sizeof(exchook_io_out_cab));
} else {
memset(&exchook_io_out_pad_p1, 0, sizeof(exchook_io_out_pad_p1));
memset(&exchook_io_out_pad_p2, 0, sizeof(exchook_io_out_pad_p2));
memset(&exchook_io_out_cab, 0, sizeof(exchook_io_out_cab));
}
exchook_piuio_api.set_output_pad(0, &exchook_io_out_pad_p1);
exchook_piuio_api.set_output_pad(1, &exchook_io_out_pad_p2);
exchook_piuio_api.set_output_cab(&exchook_io_out_cab);
return 0;
}
static int32_t exchook_io_light_neon(void* obj, uint8_t on)
{
if (on) {
exchook_io_out_cab.bass = true;
} else {
exchook_io_out_cab.bass = false;
}
exchook_piuio_api.set_output_cab(&exchook_io_out_cab);
return 0;
}
static int32_t exchook_io_light_halogan(void* obj, uint8_t on, uint16_t light_id)
{
if (light_id & 0x0400) {
exchook_io_out_cab.halo_l1 = on;
}
if (light_id & 0x0200) {
exchook_io_out_cab.halo_l2 = on;
}
if (light_id & 0x0100) {
exchook_io_out_cab.halo_r1 = on;
}
if (light_id & 0x0080) {
exchook_io_out_cab.halo_r2 = on;
}
exchook_piuio_api.set_output_cab(&exchook_io_out_cab);
return 0;
}
static int32_t exchook_io_light_p1_ccfl(void* obj, uint8_t on, uint16_t cmd)
{
if (cmd & 0x0004) {
exchook_io_out_pad_p1.lu = on;
}
if (cmd & 0x0008) {
exchook_io_out_pad_p1.ru = on;
}
if (cmd & 0x0010) {
exchook_io_out_pad_p1.cn = on;
}
if (cmd & 0x0020) {
exchook_io_out_pad_p1.ld = on;
}
if (cmd & 0x040) {
exchook_io_out_pad_p1.rd = on;
}
return 0;
}
static int32_t exchook_io_light_p2_ccfl(void* obj, uint8_t on, uint16_t cmd)
{
if (cmd & 0x0004) {
exchook_io_out_pad_p2.lu = on;
}
if (cmd & 0x0008) {
exchook_io_out_pad_p2.ru = on;
}
if (cmd & 0x0010) {
exchook_io_out_pad_p2.cn = on;
}
if (cmd & 0x0020) {
exchook_io_out_pad_p2.ld = on;
}
if (cmd & 0x040) {
exchook_io_out_pad_p2.rd = on;
}
return 0;
}
static int32_t exchook_io_input(void* obj)
{
uint32_t stat;
uint32_t stat_down;
uint32_t stat_up;
struct ptapi_io_piuio_sys_inputs sys_in;
memset(&sys_in, 0, sizeof(struct ptapi_io_piuio_sys_inputs));
exchook_piuio_api.get_input_sys(&sys_in);
/* For game loaders and stuff */
if (exchook_io_exit_on_service_test) {
if (sys_in.test && sys_in.service) {
log_info("Exit on service + test enabled and hit, bye");
exit(0);
}
}
/* process final input states for the game as well as service, test, clear
and coin which can't be handled in the check function */
stat = 0;
stat_down = 0;
stat_up = 0;
/* Player 1 */
if (exchook_io_sensores_p1.lu) {
stat |= GI_P1_7;
}
if (exchook_io_sensores_p1.ru) {
stat |= GI_P1_9;
}
if (exchook_io_sensores_p1.cn) {
stat |= GI_P1_5;
}
if (exchook_io_sensores_p1.ld) {
stat |= GI_P1_1;
}
if (exchook_io_sensores_p1.rd) {
stat |= GI_P1_3;
}
/* Player 2 */
if (exchook_io_sensores_p2.lu) {
stat |= GI_P2_7;
}
if (exchook_io_sensores_p2.ru) {
stat |= GI_P2_9;
}
if (exchook_io_sensores_p2.cn) {
stat |= GI_P2_5;
}
if (exchook_io_sensores_p2.ld) {
stat |= GI_P2_1;
}
if (exchook_io_sensores_p2.rd) {
stat |= GI_P2_3;
}
/* Sys */
if (sys_in.test) {
stat |= GI_TEST;
}
if (sys_in.service) {
stat |= GI_SERVICE;
}
/* Taken from source but doesn't seem to work (on some versions?) */
if (sys_in.clear) {
stat |= GI_CLEAR;
}
if (sys_in.coin) {
stat |= GI_COIN;
}
if (sys_in.coin2) {
stat |= GI_COIN2;
}
/* iterate bits for up and down events */
for (uint8_t i = 0; i < 32; i++) {
if (!(exchook_io_game_input_stat_prev & (1 << i)) &&
(stat & (1 << i))) {
/* New push down */
stat_down |= (1 << i);
} else if ((exchook_io_game_input_stat_prev & (1 << i)) &&
!(stat & (1 << i))) {
/* New release */
stat_up |= (1 << i);
}
}
/* Extremely bad IO subsystem design */
*exchook_io_game_input_stat |= stat;
*exchook_io_game_input_down |= stat_down;
*exchook_io_game_input_up = stat_up;
/* swap for next iterateion */
exchook_io_game_input_stat_prev = *exchook_io_game_input_stat;
/* Welp, the game expects us to do output setting if a button is pressed
here...fuck that mess. Merge (i.e. |= not =) with other states set
by the game */
memset(&exchook_io_out_pad_press_p1, 0, sizeof(exchook_io_out_pad_press_p1));
memset(&exchook_io_out_pad_press_p2, 0, sizeof(exchook_io_out_pad_press_p2));
/* Player 1 */
exchook_io_out_pad_press_p1.lu |= exchook_io_sensores_p1.lu;
exchook_io_out_pad_press_p1.ru |= exchook_io_sensores_p1.ru;
exchook_io_out_pad_press_p1.cn |= exchook_io_sensores_p1.cn;
exchook_io_out_pad_press_p1.ld |= exchook_io_sensores_p1.ld;
exchook_io_out_pad_press_p1.rd |= exchook_io_sensores_p1.rd;
/* Player 2 */
exchook_io_out_pad_press_p2.lu |= exchook_io_sensores_p2.lu;
exchook_io_out_pad_press_p2.ru |= exchook_io_sensores_p2.ru;
exchook_io_out_pad_press_p2.cn |= exchook_io_sensores_p2.cn;
exchook_io_out_pad_press_p2.ld |= exchook_io_sensores_p2.ld;
exchook_io_out_pad_press_p2.rd |= exchook_io_sensores_p2.rd;
/* merge */
/* Player 1 */
exchook_io_out_pad_press_p1.lu |= exchook_io_out_pad_p1.lu;
exchook_io_out_pad_press_p1.ru |= exchook_io_out_pad_p1.ru;
exchook_io_out_pad_press_p1.cn |= exchook_io_out_pad_p1.cn;
exchook_io_out_pad_press_p1.ld |= exchook_io_out_pad_p1.ld;
exchook_io_out_pad_press_p1.rd |= exchook_io_out_pad_p1.rd;
/* Player 2 */
exchook_io_out_pad_press_p2.lu |= exchook_io_out_pad_p2.lu;
exchook_io_out_pad_press_p2.ru |= exchook_io_out_pad_p2.ru;
exchook_io_out_pad_press_p2.cn |= exchook_io_out_pad_p2.cn;
exchook_io_out_pad_press_p2.ld |= exchook_io_out_pad_p2.ld;
exchook_io_out_pad_press_p2.rd |= exchook_io_out_pad_p2.rd;
exchook_piuio_api.set_output_pad(0, &exchook_io_out_pad_press_p1);
exchook_piuio_api.set_output_pad(1, &exchook_io_out_pad_press_p2);
return 0;
}
static int32_t exchook_io_coin_input(void* obj)
{
/* Stub */
return 0;
}
static int32_t exchook_io_coin_input_2(void* obj)
{
/* Stub */
return 0;
}
static int32_t exchook_io_coin_counter_2(void* obj)
{
/* Stub */
return 0;
}
+26
View File
@@ -0,0 +1,26 @@
/**
* Patch module hooking the MK5IO engine code of exceed to inject our own
* IO code to run the game on newer versions of the piuio as well as custom
* IO devices
*/
#ifndef EXCHOOK_IO_H
#define EXCHOOK_IO_H
#include "hook/exc/mempatch.h"
/**
* Initialize MK5IO emulation for exceed
*
* @param patch_table Patch table with memory addresses
* @param piuio_lib_path Path to piuio lib to use for emulation
* @parma exit_on_service_test Exit the game when pressing service + test
*/
void exchook_io_init(const struct exchook_mempatch_table* patch_table, const char* piuio_lib_path,
bool exit_on_service_test);
/**
* Shut down io emulation
*/
void exchook_io_shutdown(void);
#endif
+43
View File
@@ -0,0 +1,43 @@
#define LOG_MODULE "exchook-lockchip"
#include <stdlib.h>
#include "sec/lockchip/lockchip.h"
#include "sec/lockchip/lockchip-defs.h"
#include "util/log.h"
#include "util/patch.h"
#include "lockchip.h"
static void exchook_lockchip_dec_chunk(uint8_t len, const uint8_t* src,
uint8_t* dest);
void exchook_lockchip_init(const struct exchook_mempatch_table* patch_table)
{
sec_lockchip_init(
sec_lockchip_defs_transforms[SEC_LOCK_CHIP_DEFS_GAME_EXCEED]);
/* Hook lockchip */
util_patch_function(patch_table->addr_lockchip_dec_chunk, exchook_lockchip_dec_chunk);
log_info("Initialized");
}
static void exchook_lockchip_dec_chunk(uint8_t len, const uint8_t* src,
uint8_t* dest)
{
sec_lockchip_start();
uint8_t last_res = sec_lockchip_step(src[0] ^ 0xFF);
for (size_t i = 1; i < len; i++)
{
uint8_t result = sec_lockchip_step(src[i] ^ 0xFF);
dest[i - 1] = (uint8_t)((last_res >> 3) ^ (result << 5));
last_res = result;
}
uint8_t last_byte = sec_lockchip_step(0);
dest[len - 1] = (uint8_t)((last_res >> 3) ^ (last_byte << 5));
}
+19
View File
@@ -0,0 +1,19 @@
/**
* Patch module hooking lockchip calls to a lockchip emulator to decrypt
* game assets and run the game
*/
#ifndef EXCHOOK_LOCKCHIP_H
#define EXCHOOK_LOCKCHIP_H
#include <stdint.h>
#include "hook/exc/mempatch.h"
/**
* Initialize lockchip emulation for exceed
*
* @param patch_table Patch table with memory addresses
*/
void exchook_lockchip_init(const struct exchook_mempatch_table* patch_table);
#endif
+256
View File
@@ -0,0 +1,256 @@
/**
* Hook library for Pump It Up: Exceed
*/
#define LOG_MODULE "exchook"
#include <string.h>
#include "capnhook/hook/lib-main.h"
#include "capnhook/hooklib/redir.h"
#include "hook/core/piu-utils.h"
#include "hook/patch/asound-fix.h"
#include "hook/patch/gfx.h"
#include "hook/patch/hook-mon.h"
#include "hook/patch/main-loop.h"
#include "hook/patch/redir.h"
#include "hook/patch/sigsegv.h"
#include "hook/patch/sound.h"
#include "util/fs.h"
#include "util/glibc.h"
#include "util/patch.h"
#include "util/str.h"
#include "util/sys-info.h"
#include "eeprom.h"
#include "io.h"
#include "lockchip.h"
#include "mempatch.h"
#include "options.h"
static void exchook_bootstrapping(int argc, char** argv, struct exchook_options* options, char** game_data_path)
{
log_assert(argc >= 0);
log_assert(argv);
log_assert(options);
log_assert(game_data_path);
if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) {
return;
}
hook_core_piu_log_init(argc, argv);
if (!exchook_options_init(argc, argv, options)) {
exit(0);
}
hook_core_piu_log_info(argc, argv);
*game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv);
hook_core_piu_utils_verify_root_user();
log_info("Initializing, piu game data path: %s", *game_data_path);
}
static void exchook_game_version_mempatch_table_init(
struct exchook_options* options,
const struct exchook_mempatch_table** patch_table)
{
log_assert(options);
log_assert(patch_table);
if (!options->game.version) {
log_die("No game version specified");
} else {
log_info("Game version configured: %s", options->game.version);
}
*patch_table = exchook_mempatch_get_table(options->game.version);
if (*patch_table == NULL) {
log_die("Unsupported game version %s", options->game.version);
}
}
static void exchook_patch_fs_init(struct exchook_options* options)
{
log_assert(options);
patch_hook_mon_init(
options->patch.hook_mon.io,
options->patch.hook_mon.file,
options->patch.hook_mon.fs,
options->patch.hook_mon.usb,
options->patch.hook_mon.open);
patch_redir_init();
}
static void exchook_fs_redir_settings(struct exchook_options* options)
{
log_assert(options);
if (options->game.settings) {
if (!util_fs_path_exists(options->game.settings)) {
log_info("Settings path %s does not exist, creating", options->game.settings);
if (!util_fs_mkdir(options->game.settings)) {
log_error("Creating directory %s failed", options->game.settings);
}
}
char* abs_path = util_fs_get_abs_path(options->game.settings);
cnh_redir_add("/SETTINGS", abs_path);
free(abs_path);
} else {
log_warn("No settings path specified");
}
}
static void exchook_fs_redir_usb()
{
/* Needed on newer kernels */
cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices");
}
static void exchook_patch_gfx_init(struct exchook_options* options)
{
log_assert(options);
patch_gfx_init();
if (options->patch.gfx.windowed) {
patch_gfx_force_window_mode();
}
}
static void exchook_patch_main_loop_init(struct exchook_options* options)
{
log_assert(options);
patch_main_loop_init(true, options->patch.main_loop.disable_built_in_inputs, false);
if (options->patch.main_loop.x11_input_handler_api_lib) {
char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib);
patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib);
free(abs_path_x11_input_hook_lib);
}
}
static void exchook_patch_sound_init(struct exchook_options* options, const struct exchook_mempatch_table* patch_table)
{
log_assert(options);
log_assert(patch_table);
/* exceed crashes with its default device hw:0. use dmix instead */
if (strcmp(options->patch.sound.device, "dmix") != 0) {
log_warn("Game might crash with selected sound device %s, use 'dmix' instead if that happens",
options->patch.sound.device);
}
patch_sound_init(options->patch.sound.device);
patch_asound_fix_init();
/* return from StopAllEffects to avoid killing the sound thread */
util_patch_write_memory_byte(patch_table->addr_stop_all_effects_return, 0xC3);
}
static void exchook_patch_sigsegv_init(struct exchook_options* options)
{
log_assert(options);
patch_sigsegv_init(options->patch.sigsegv.halt_on_segv);
}
static void exchook_patch_lockchip_init(const struct exchook_mempatch_table* patch_table)
{
log_assert(patch_table);
exchook_lockchip_init(patch_table);
}
static void exchook_patch_eeprom_init(struct exchook_options* options, const struct exchook_mempatch_table* patch_table)
{
log_assert(options);
log_assert(patch_table);
char* eeprom_path;
if (options->game.settings) {
char* abs_path = util_fs_get_abs_path(options->game.settings);
eeprom_path = util_str_merge(abs_path, "/eeprom.bin");
free(abs_path);
} else {
eeprom_path = util_str_dup("/tmp/eeprom.bin");
log_warn("No settings path specified to store eeprom data, defaulting to %s", eeprom_path);
}
exchook_eeprom_init(patch_table, eeprom_path);
free(eeprom_path);
}
static void exchook_patch_piuio_init(struct exchook_options* options, const struct exchook_mempatch_table* patch_table)
{
log_assert(options);
log_assert(patch_table);
if (options->patch.piuio.api_lib) {
char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib);
exchook_io_init(patch_table, abs_path_iolib, options->patch.piuio.exit_test_serv);
free(abs_path_iolib);
}
}
void exchook_constructor(void)
{
/* Nothing here */
}
void exchook_destructor(void)
{
/* Nothing here */
}
void exchook_trap_before_main(int argc, char** argv)
{
log_assert(argc >= 0);
log_assert(argv);
char* game_data_path;
struct exchook_options options;
const struct exchook_mempatch_table* mempatch_table;
exchook_bootstrapping(argc, argv, &options, &game_data_path);
exchook_game_version_mempatch_table_init(&options, &mempatch_table);
exchook_patch_fs_init(&options);
exchook_fs_redir_settings(&options);
exchook_fs_redir_usb();
exchook_patch_gfx_init(&options);
exchook_patch_main_loop_init(&options);
exchook_patch_sound_init(&options, mempatch_table);
exchook_patch_sigsegv_init(&options);
exchook_patch_lockchip_init(mempatch_table);
exchook_patch_eeprom_init(&options, mempatch_table);
exchook_patch_piuio_init(&options, mempatch_table);
free(game_data_path);
log_info("Hooking finished");
}
void exchook_trap_after_main(void)
{
exchook_io_shutdown();
}
LIB_MAIN_CONSTRUCTOR(exchook_constructor);
LIB_MAIN_DESTRUCTOR(exchook_destructor);
LIB_MAIN_TRAP_MAIN(exchook_trap_before_main, exchook_trap_after_main);
+62
View File
@@ -0,0 +1,62 @@
#include <stdlib.h>
#include <string.h>
#include "hook/exc/mempatch.h"
#include "util/defs.h"
static const struct exchook_mempatch_table _exchook_mempatch_table[] = {
{
.version = "20040325",
.addr_eeprom_read = 0x080716E0,
.addr_eeprom_write = 0x08071620,
.addr_io_check = 0x0806DEB0,
.addr_io_light_all = 0x0806E5A0,
.addr_io_light_neon = 0x0806E600,
.addr_io_light_halogan = 0x0806E630,
.addr_io_light_p1_ccfl = 0x0806E660,
.addr_io_light_p2_ccfl = 0x0806E690,
.addr_io_input = 0x0806E6C0,
.addr_io_coin_input = 0x0806EE20,
.addr_io_coin_input_2 = 0x0806EEE0,
.addr_io_coin_counter_2 = 0x0806EFA0,
.addr_io_game_input_stat = 0x080B9410,
.addr_io_game_input_down = 0x080B9408,
.addr_io_game_input_up = 0x080B940C,
.addr_lockchip_dec_chunk = 0x08071E80,
.addr_stop_all_effects_return = 0x0806F6E0,
},
{
.version = "20040408",
.addr_eeprom_read = 0x08071CC0,
.addr_eeprom_write = 0x08071C00,
.addr_io_check = 0x0806E550,
.addr_io_light_all = 0x0806EC40,
.addr_io_light_neon = 0x0806ECA0,
.addr_io_light_halogan = 0x0806ECD0,
.addr_io_light_p1_ccfl = 0x0806ED00,
.addr_io_light_p2_ccfl = 0x0806ED30,
.addr_io_input = 0x0806ED60,
.addr_io_coin_input = 0x0806F4C0,
.addr_io_coin_input_2 = 0x0806F580,
.addr_io_coin_counter_2 = 0x0806F640,
.addr_io_game_input_stat = 0x080B5290,
.addr_io_game_input_down = 0x080B5288,
.addr_io_game_input_up = 0x080B528C,
.addr_lockchip_dec_chunk = 0x08072460,
.addr_stop_all_effects_return = 0x0806FD80,
},
};
const struct exchook_mempatch_table* exchook_mempatch_get_table(const char* version)
{
size_t entries = lengthof(_exchook_mempatch_table);
for (uint32_t i = 0; i < entries; i++) {
if (!strcmp(_exchook_mempatch_table[i].version, version)) {
return &_exchook_mempatch_table[i];
}
}
return NULL;
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef EXCHOOK_MEMPATCH_H
#define EXCHOOK_MEMPATCH_H
#include <stdint.h>
struct exchook_mempatch_table {
const char* version;
uintptr_t addr_eeprom_read;
uintptr_t addr_eeprom_write;
uintptr_t addr_io_check;
uintptr_t addr_io_light_all;
uintptr_t addr_io_light_neon;
uintptr_t addr_io_light_halogan;
uintptr_t addr_io_light_p1_ccfl;
uintptr_t addr_io_light_p2_ccfl;
uintptr_t addr_io_input;
uintptr_t addr_io_coin_input;
uintptr_t addr_io_coin_input_2;
uintptr_t addr_io_coin_counter_2;
uintptr_t addr_io_game_input_stat;
uintptr_t addr_io_game_input_down;
uintptr_t addr_io_game_input_up;
uintptr_t addr_lockchip_dec_chunk;
uintptr_t addr_stop_all_effects_return;
};
const struct exchook_mempatch_table* exchook_mempatch_get_table(const char* version);
#endif
+184
View File
@@ -0,0 +1,184 @@
#include "hook/exc/options.h"
#include "util/options.h"
#define EXCHOOK_OPTIONS_STR_GAME_SETTINGS "game.settings"
#define EXCHOOK_OPTIONS_STR_GAME_VERSION "game.version"
#define EXCHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed"
#define EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file"
#define EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs"
#define EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io"
#define EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open"
#define EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb"
#define EXCHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs"
#define EXCHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler"
#define EXCHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib"
#define EXCHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv"
#define EXCHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device"
#define EXCHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv"
#define EXCHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file"
#define EXCHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level"
static const struct util_options_def exchook_options_def[] = {
{
.name = EXCHOOK_OPTIONS_STR_GAME_SETTINGS,
.description = "Path to game settings (SETTINGS) folder",
.param = 's',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "./save",
},
{
.name = EXCHOOK_OPTIONS_STR_GAME_VERSION,
.description = "Version of the game (format YYYYMMDD). Make sure this matches exactly the version you are"
" running because the hook lib has to apply memory patches.",
.param = 'v',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "20040408",
},
{
.name = EXCHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED,
.description = "Force game into window mode",
.param = 'w',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE,
.description = "Enable file call monitoring",
.param = 'f',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS,
.description = "Enable file system related call monitoring",
.param = 'g',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO,
.description = "Enable IO syscall monitoring",
.param = 'i',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN,
.description = "Enable any file/IO open call monitoring",
.param = 'j',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB,
.description = "Enable libusb call monitoring",
.param = 'u',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = EXCHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS,
.description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)",
.param = 'k',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = EXCHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER,
.description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs",
.param = 'q',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = EXCHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB,
.description = "Path to library implementing the piuio api for piuio emulation",
.param = 'p',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = EXCHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV,
.description = "Enable game exit on Test + Service",
.param = 'e',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = EXCHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE,
.description = "Select the sound device to open on snd_pcm_open",
.param = 'a',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "dmix",
},
{
.name = EXCHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV,
.description = "Halt on sigsegv to attach a debugger to the process",
.param = 'd',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.str = false,
},
{
.name = EXCHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE,
.description = "Print the log output to the specified file",
.param = 'o',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "pumptools.log",
},
{
.name = EXCHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL,
.description = "Set the log level (0-4)",
.param = 'l',
.type = UTIL_OPTIONS_TYPE_INT,
.default_value.i = LOG_LEVEL_DEBUG,
},
};
static const struct util_options_defs exchook_options_defs = {
.usage_header =
"Pumptools exchook for Pump It Up: Exceed, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n"
"Usage: LD_PRELOAD=./exchook.so [game exec] [game data path] <options>",
.usage_param = 'h',
.defs = exchook_options_def,
.ndefs = lengthof(exchook_options_def)
};
bool exchook_options_init(int argc, char** argv, struct exchook_options* options)
{
log_assert(argv);
log_assert(options);
struct util_options_opts* options_opt;
util_options_init(argc, argv);
options_opt = util_options_get(&exchook_options_defs);
if (!options_opt) {
return false;
}
options->game.settings = util_options_get_str(options_opt, EXCHOOK_OPTIONS_STR_GAME_SETTINGS);
options->game.version = util_options_get_str(options_opt, EXCHOOK_OPTIONS_STR_GAME_VERSION);
options->patch.gfx.windowed = util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED);
options->patch.hook_mon.file = util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE);
options->patch.hook_mon.fs = util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS);
options->patch.hook_mon.io = util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO);
options->patch.hook_mon.open = util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN);
options->patch.hook_mon.usb = util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB);
options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt,
EXCHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS);
options->patch.main_loop.x11_input_handler_api_lib =
util_options_get_str(options_opt, EXCHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER);
options->patch.piuio.api_lib = util_options_get_str(options_opt, EXCHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB);
options->patch.piuio.exit_test_serv =
util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV);
options->patch.sound.device = util_options_get_str(options_opt, EXCHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE);
options->patch.sigsegv.halt_on_segv =
util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV);
options->log.file = util_options_get_str(options_opt, EXCHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE);
options->log.level =
(enum util_log_level) util_options_get_int(options_opt, EXCHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL);
return true;
}
+55
View File
@@ -0,0 +1,55 @@
#ifndef EXCHOOK_OPTIONS_H
#define EXCHOOK_OPTIONS_H
#include <stdbool.h>
#include <stdint.h>
#include "util/log.h"
struct exchook_options {
struct game {
const char* settings;
const char* version;
} game;
struct patch {
struct gfx {
bool windowed;
} gfx;
struct hook_mon {
bool file;
bool fs;
bool io;
bool open;
bool usb;
} hook_mon;
struct main_loop {
bool disable_built_in_inputs;
const char* x11_input_handler_api_lib;
} main_loop;
struct piuio {
const char* api_lib;
bool exit_test_serv;
} piuio;
struct sound {
const char* device;
} sound;
struct sigsegv {
bool halt_on_segv;
} sigsegv;
} patch;
struct log {
const char* file;
enum util_log_level level;
} log;
};
bool exchook_options_init(int argc, char** argv, struct exchook_options* options);
#endif
+1
View File
@@ -0,0 +1 @@
DUMMY NOT RELEASED YET
+1
View File
@@ -0,0 +1 @@
DUMMY NOT RELEASED YET
+244
View File
@@ -0,0 +1,244 @@
/**
* Hook library for Pump It Up: Fiesta 2
*/
#define LOG_MODULE "f2hook"
#include <string.h>
#include "capnhook/hook/lib-main.h"
#include "capnhook/hooklib/redir.h"
#include "hook/core/piu-utils.h"
#include "hook/patch/amixer-block.h"
#include "hook/patch/gfx.h"
#include "hook/patch/hasp.h"
#include "hook/patch/hdd-check.h"
#include "hook/patch/hook-mon.h"
#include "hook/patch/main-loop.h"
#include "hook/patch/mounts.h"
#include "hook/patch/piuio.h"
#include "hook/patch/piuio-exit.h"
#include "hook/patch/redir.h"
#include "hook/patch/sigsegv.h"
#include "hook/patch/sound.h"
#include "hook/patch/usb-emu.h"
#include "hook/patch/usb-init-fix.h"
#include "hook/patch/usb-mnt.h"
#include "hook/patch/usbfs.h"
#include "util/fs.h"
#include "util/glibc.h"
#include "util/sys-info.h"
#include "options.h"
/* Compiled binary data from data folder. Symbols are defined by compiler */
extern const uint8_t _binary_f2hook_hdd_raw_start[];
extern const uint8_t _binary_f2hook_hdd_raw_end[];
extern const uint8_t _binary_f2hook_hasp_key_start[];
extern const uint8_t _binary_f2hook_hasp_key_end[];
static void f2hook_bootstrapping(int argc, char** argv, struct f2hook_options* options, char** game_data_path)
{
log_assert(argc >= 0);
log_assert(argv);
log_assert(options);
log_assert(game_data_path);
if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) {
return;
}
hook_core_piu_log_init(argc, argv);
if (!f2hook_options_init(argc, argv, options)) {
exit(0);
}
hook_core_piu_log_info(argc, argv);
*game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv);
hook_core_piu_utils_verify_root_user();
hook_core_piu_utils_fix_locale();
log_info("Initializing, piu game data path: %s", *game_data_path);
}
static void f2hook_patch_fs_init(struct f2hook_options* options)
{
log_assert(options);
patch_hook_mon_init(
options->patch.hook_mon.io,
options->patch.hook_mon.file,
options->patch.hook_mon.fs,
options->patch.hook_mon.usb,
options->patch.hook_mon.open);
patch_redir_init();
}
static void f2hook_fs_redir_settings(struct f2hook_options* options)
{
log_assert(options);
if (options->game.settings) {
if (!util_fs_path_exists(options->game.settings)) {
log_info("Settings path %s does not exist, creating", options->game.settings);
if (!util_fs_mkdir(options->game.settings)) {
log_error("Creating directory %s failed", options->game.settings);
}
}
char* abs_path = util_fs_get_abs_path(options->game.settings);
cnh_redir_add("/SETTINGS", abs_path);
free(abs_path);
} else {
log_warn("No settings path specified");
}
}
static void f2hook_fs_redir_usb()
{
/* Needed on newer kernels */
cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices");
patch_usbfs_init();
}
static void f2hook_fs_redirs_init(struct f2hook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
f2hook_fs_redir_settings(options);
f2hook_fs_redir_usb();
}
static void f2hook_patch_fs_mounting_init()
{
patch_mounts_init();
patch_usb_mnt_init();
}
static void f2hook_patch_gfx_init(struct f2hook_options* options)
{
log_assert(options);
patch_gfx_init();
if (options->patch.gfx.windowed) {
patch_gfx_force_window_mode();
}
}
static void f2hook_patch_main_loop_init(struct f2hook_options* options)
{
log_assert(options);
patch_main_loop_init(false, options->patch.main_loop.disable_built_in_inputs, false);
if (options->patch.main_loop.x11_input_handler_api_lib) {
char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib);
patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib);
free(abs_path_x11_input_hook_lib);
}
}
static void f2hook_patch_sound_init(struct f2hook_options* options)
{
log_assert(options);
patch_sound_init(options->patch.sound.device);
patch_axmier_block_init();
}
static void f2hook_patch_sigsegv_init(struct f2hook_options* options)
{
log_assert(options);
patch_sigsegv_init(options->patch.sigsegv.halt_on_segv);
}
static void f2hook_patch_dongle_init()
{
patch_hasp_init((const uint8_t*) _binary_f2hook_hasp_key_start,
((uintptr_t) &_binary_f2hook_hasp_key_end - (uintptr_t) &_binary_f2hook_hasp_key_start));
}
static void f2hook_patch_hdd_check_init()
{
patch_hdd_check_init((const uint8_t*) _binary_f2hook_hdd_raw_start,
((uintptr_t) &_binary_f2hook_hdd_raw_end - (uintptr_t) &_binary_f2hook_hdd_raw_start));
}
static void f2hook_patch_piuio_init(struct f2hook_options* options)
{
log_assert(options);
/* Hook before IO emulation */
if (options->patch.piuio.exit_test_serv) {
patch_piuio_exit_init();
}
patch_usb_init_fix_init();
patch_usb_emu_init();
if (options->patch.piuio.api_lib) {
char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib);
patch_piuio_init(abs_path_iolib);
free(abs_path_iolib);
}
}
void f2hook_constructor(void)
{
/* Nothing here */
}
void f2hook_destructor(void)
{
/* Nothing here */
}
void f2hook_trap_before_main(int argc, char** argv)
{
log_assert(argc >= 0);
log_assert(argv);
char* game_data_path;
struct f2hook_options options;
f2hook_bootstrapping(argc, argv, &options, &game_data_path);
f2hook_patch_fs_init(&options);
f2hook_fs_redirs_init(&options, game_data_path);
f2hook_patch_fs_mounting_init();
f2hook_patch_gfx_init(&options);
f2hook_patch_main_loop_init(&options);
f2hook_patch_sound_init(&options);
f2hook_patch_sigsegv_init(&options);
f2hook_patch_dongle_init();
f2hook_patch_hdd_check_init();
f2hook_patch_piuio_init(&options);
free(game_data_path);
log_info("Hooking finished");
}
void f2hook_trap_after_main(void)
{
patch_piuio_shutdown();
}
LIB_MAIN_CONSTRUCTOR(f2hook_constructor);
LIB_MAIN_DESTRUCTOR(f2hook_destructor);
LIB_MAIN_TRAP_MAIN(f2hook_trap_before_main, f2hook_trap_after_main);
+195
View File
@@ -0,0 +1,195 @@
#include "hook/f2/options.h"
#include "util/options.h"
#define F2HOOK_OPTIONS_STR_GAME_SETTINGS "game.settings"
#define F2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed"
#define F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file"
#define F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs"
#define F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io"
#define F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open"
#define F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb"
#define F2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs"
#define F2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler"
#define F2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER "patch.net_profile.server"
#define F2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID "patch.net_profile.machine_id"
#define F2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib"
#define F2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv"
#define F2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device"
#define F2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv"
#define F2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file"
#define F2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level"
const struct util_options_def f2hook_options_def[] = {
{
.name = F2HOOK_OPTIONS_STR_GAME_SETTINGS,
.description = "Path to game settings (SETTINGS) folder",
.param = 's',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "./save",
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED,
.description = "Force game into window mode",
.param = 'w',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE,
.description = "Enable file call monitoring",
.param = 'f',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS,
.description = "Enable file system related call monitoring",
.param = 'g',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO,
.description = "Enable IO syscall monitoring",
.param = 'i',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN,
.description = "Enable any file/IO open call monitoring",
.param = 'j',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB,
.description = "Enable libusb call monitoring",
.param = 'u',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS,
.description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)",
.param = 'k',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER,
.description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs",
.param = 'q',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER,
.description = "Address and port of pumpnet server (e.g. 127.0.0.1:1234)",
.param = 'n',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID,
.description = "Machine id for pumpnet",
.param = 'm',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "0",
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB,
.description = "Path to library implementing the piuio api for piuio emulation",
.param = 'p',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV,
.description = "Enable game exit on Test + Service",
.param = 'e',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE,
.description = "Select the sound device to open on snd_pcm_open",
.param = 'a',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "dmix",
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV,
.description = "Halt on sigsegv to attach a debugger to the process",
.param = 'd',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.str = false,
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE,
.description = "Print the log output to the specified file",
.param = 'o',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "pumptools.log",
},
{
.name = F2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL,
.description = "Set the log level (0-4)",
.param = 'l',
.type = UTIL_OPTIONS_TYPE_INT,
.default_value.i = LOG_LEVEL_DEBUG,
},
};
const struct util_options_defs f2hook_options_defs = {
.usage_header =
"Pumptools f2hook for Pump It Up: Fiesta 2, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n"
"Usage: LD_PRELOAD=./f2hook.so [game exec] [game data path] <options>",
.usage_param = 'h',
.defs = f2hook_options_def,
.ndefs = lengthof(f2hook_options_def)
};
bool f2hook_options_init(int argc, char** argv, struct f2hook_options* options)
{
log_assert(argv);
log_assert(options);
struct util_options_opts* options_opt;
util_options_init(argc, argv);
options_opt = util_options_get(&f2hook_options_defs);
if (!options_opt) {
return false;
}
options->game.settings = util_options_get_str(options_opt, F2HOOK_OPTIONS_STR_GAME_SETTINGS);
options->patch.gfx.windowed = util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED);
options->patch.hook_mon.file = util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE);
options->patch.hook_mon.fs = util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS);
options->patch.hook_mon.io = util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO);
options->patch.hook_mon.open = util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN);
options->patch.hook_mon.usb = util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB);
options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt,
F2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS);
options->patch.main_loop.x11_input_handler_api_lib =
util_options_get_str(options_opt, F2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER);
options->patch.net.server = util_options_get_str(options_opt, F2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER);
options->patch.net.machine_id = strtoul(
util_options_get_str(options_opt, F2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID),
NULL,
16);
options->patch.piuio.api_lib = util_options_get_str(options_opt, F2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB);
options->patch.piuio.exit_test_serv =
util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV);
options->patch.sound.device = util_options_get_str(options_opt, F2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE);
options->patch.sigsegv.halt_on_segv =
util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV);
options->log.file = util_options_get_str(options_opt, F2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE);
options->log.level =
(enum util_log_level) util_options_get_int(options_opt, F2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL);
return true;
}
+59
View File
@@ -0,0 +1,59 @@
#ifndef F2HOOK_OPTIONS_H
#define F2HOOK_OPTIONS_H
#include <stdbool.h>
#include <stdint.h>
#include "util/log.h"
struct f2hook_options {
struct game {
const char* settings;
} game;
struct patch {
struct gfx {
bool windowed;
} gfx;
struct hook_mon {
bool file;
bool fs;
bool io;
bool open;
bool usb;
} hook_mon;
struct main_loop {
bool disable_built_in_inputs;
const char* x11_input_handler_api_lib;
} main_loop;
struct net {
const char* server;
uint32_t machine_id;
} net;
struct piuio {
const char* api_lib;
bool exit_test_serv;
} piuio;
struct sound {
const char* device;
} sound;
struct sigsegv {
bool halt_on_segv;
} sigsegv;
} patch;
struct log {
const char* file;
enum util_log_level level;
} log;
};
bool f2hook_options_init(int argc, char** argv, struct f2hook_options* options);
#endif
+1
View File
@@ -0,0 +1 @@
DUMMY NOT RELEASED YET
+1
View File
@@ -0,0 +1 @@
DUMMY NOT RELEASED YET
+242
View File
@@ -0,0 +1,242 @@
/**
* Hook library for Pump It Up: FiestaEX
*/
#define LOG_MODULE "fexhook"
#include <string.h>
#include "capnhook/hook/lib-main.h"
#include "capnhook/hooklib/redir.h"
#include "hook/core/piu-utils.h"
#include "hook/patch/amixer-block.h"
#include "hook/patch/gfx.h"
#include "hook/patch/hdd-check.h"
#include "hook/patch/hook-mon.h"
#include "hook/patch/main-loop.h"
#include "hook/patch/microdog40.h"
#include "hook/patch/mounts.h"
#include "hook/patch/piuio.h"
#include "hook/patch/piuio-exit.h"
#include "hook/patch/redir.h"
#include "hook/patch/sigsegv.h"
#include "hook/patch/sound.h"
#include "hook/patch/usb-emu.h"
#include "hook/patch/usb-init-fix.h"
#include "hook/patch/usb-mnt.h"
#include "hook/patch/usbfs.h"
#include "util/fs.h"
#include "util/glibc.h"
#include "util/sys-info.h"
#include "options.h"
/* Compiled binary data from data folder. Symbols are defined by compiler */
extern const uint8_t _binary_fexhook_hdd_raw_start[];
extern const uint8_t _binary_fexhook_hdd_raw_end[];
extern const uint8_t _binary_fexhook_dog_key_start[];
extern const uint8_t _binary_fexhook_dog_key_end[];
static void fexhook_bootstrapping(int argc, char** argv, struct fexhook_options* options, char** game_data_path)
{
log_assert(argc >= 0);
log_assert(argv);
log_assert(options);
log_assert(game_data_path);
if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) {
return;
}
hook_core_piu_log_init(argc, argv);
if (!fexhook_options_init(argc, argv, options)) {
exit(0);
}
hook_core_piu_log_info(argc, argv);
*game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv);
hook_core_piu_utils_verify_root_user();
log_info("Initializing, piu game data path: %s", *game_data_path);
}
static void fexhook_patch_fs_init(struct fexhook_options* options)
{
log_assert(options);
patch_hook_mon_init(
options->patch.hook_mon.io,
options->patch.hook_mon.file,
options->patch.hook_mon.fs,
options->patch.hook_mon.usb,
options->patch.hook_mon.open);
patch_redir_init();
}
static void fexhook_fs_redir_settings(struct fexhook_options* options)
{
log_assert(options);
if (options->game.settings) {
if (!util_fs_path_exists(options->game.settings)) {
log_info("Settings path %s does not exist, creating", options->game.settings);
if (!util_fs_mkdir(options->game.settings)) {
log_error("Creating directory %s failed", options->game.settings);
}
}
char* abs_path = util_fs_get_abs_path(options->game.settings);
cnh_redir_add("/SETTINGS", abs_path);
free(abs_path);
} else {
log_warn("No settings path specified");
}
}
static void fexhook_fs_redir_usb()
{
/* Needed on newer kernels */
cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices");
patch_usbfs_init();
}
static void fexhook_fs_redirs_init(struct fexhook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
fexhook_fs_redir_settings(options);
fexhook_fs_redir_usb();
}
static void fexhook_patch_fs_mounting_init()
{
patch_mounts_init();
patch_usb_mnt_init();
}
static void fexhook_patch_gfx_init(struct fexhook_options* options)
{
log_assert(options);
patch_gfx_init();
if (options->patch.gfx.windowed) {
patch_gfx_force_window_mode();
}
}
static void fexhook_patch_main_loop_init(struct fexhook_options* options)
{
log_assert(options);
patch_main_loop_init(false, options->patch.main_loop.disable_built_in_inputs, false);
if (options->patch.main_loop.x11_input_handler_api_lib) {
char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib);
patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib);
free(abs_path_x11_input_hook_lib);
}
}
static void fexhook_patch_sound_init(struct fexhook_options* options)
{
log_assert(options);
patch_sound_init(options->patch.sound.device);
patch_axmier_block_init();
}
static void fexhook_patch_sigsegv_init(struct fexhook_options* options)
{
log_assert(options);
patch_sigsegv_init(options->patch.sigsegv.halt_on_segv);
}
static void fexhook_patch_dongle_init()
{
patch_microdog40_init((const uint8_t*) _binary_fexhook_dog_key_start,
((uintptr_t) &_binary_fexhook_dog_key_end - (uintptr_t) &_binary_fexhook_dog_key_start));
}
static void fexhook_patch_hdd_check_init()
{
patch_hdd_check_init((const uint8_t*) _binary_fexhook_hdd_raw_start,
((uintptr_t) &_binary_fexhook_hdd_raw_end - (uintptr_t) &_binary_fexhook_hdd_raw_start));
}
static void fexhook_patch_piuio_init(struct fexhook_options* options)
{
log_assert(options);
/* Hook before IO emulation */
if (options->patch.piuio.exit_test_serv) {
patch_piuio_exit_init();
}
patch_usb_init_fix_init();
patch_usb_emu_init();
if (options->patch.piuio.api_lib) {
char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib);
patch_piuio_init(abs_path_iolib);
free(abs_path_iolib);
}
}
void fexhook_constructor(void)
{
/* Nothing here */
}
void fexhook_destructor(void)
{
/* Nothing here */
}
void fexhook_trap_before_main(int argc, char** argv)
{
log_assert(argc >= 0);
log_assert(argv);
char* game_data_path;
struct fexhook_options options;
fexhook_bootstrapping(argc, argv, &options, &game_data_path);
fexhook_patch_fs_init(&options);
fexhook_fs_redirs_init(&options, game_data_path);
fexhook_patch_fs_mounting_init();
fexhook_patch_gfx_init(&options);
fexhook_patch_main_loop_init(&options);
fexhook_patch_sound_init(&options);
fexhook_patch_sigsegv_init(&options);
fexhook_patch_dongle_init();
fexhook_patch_hdd_check_init();
fexhook_patch_piuio_init(&options);
free(game_data_path);
log_info("Hooking finished");
}
void fexhook_trap_after_main(void)
{
patch_piuio_shutdown();
}
LIB_MAIN_CONSTRUCTOR(fexhook_constructor);
LIB_MAIN_DESTRUCTOR(fexhook_destructor);
LIB_MAIN_TRAP_MAIN(fexhook_trap_before_main, fexhook_trap_after_main);
+195
View File
@@ -0,0 +1,195 @@
#include "hook/fex/options.h"
#include "util/options.h"
#define FEXHOOK_OPTIONS_STR_GAME_SETTINGS "game.settings"
#define FEXHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed"
#define FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file"
#define FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs"
#define FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io"
#define FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open"
#define FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb"
#define FEXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs"
#define FEXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler"
#define FEXHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER "patch.net_profile.server"
#define FEXHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID "patch.net_profile.machine_id"
#define FEXHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib"
#define FEXHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv"
#define FEXHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device"
#define FEXHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv"
#define FEXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file"
#define FEXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level"
const struct util_options_def fexhook_options_def[] = {
{
.name = FEXHOOK_OPTIONS_STR_GAME_SETTINGS,
.description = "Path to game settings (SETTINGS) folder",
.param = 's',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "./save",
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED,
.description = "Force game into window mode",
.param = 'w',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE,
.description = "Enable file call monitoring",
.param = 'f',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS,
.description = "Enable file system related call monitoring",
.param = 'g',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO,
.description = "Enable IO syscall monitoring",
.param = 'i',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN,
.description = "Enable any file/IO open call monitoring",
.param = 'j',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB,
.description = "Enable libusb call monitoring",
.param = 'u',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS,
.description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)",
.param = 'k',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER,
.description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs",
.param = 'q',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER,
.description = "Address and port of pumpnet server (e.g. 127.0.0.1:1234)",
.param = 'n',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID,
.description = "Machine id for pumpnet",
.param = 'm',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "0",
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB,
.description = "Path to library implementing the piuio api for piuio emulation",
.param = 'p',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV,
.description = "Enable game exit on Test + Service",
.param = 'e',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE,
.description = "Select the sound device to open on snd_pcm_open",
.param = 'a',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "dmix",
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV,
.description = "Halt on sigsegv to attach a debugger to the process",
.param = 'd',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.str = false,
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE,
.description = "Print the log output to the specified file",
.param = 'o',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "pumptools.log",
},
{
.name = FEXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL,
.description = "Set the log level (0-4)",
.param = 'l',
.type = UTIL_OPTIONS_TYPE_INT,
.default_value.i = LOG_LEVEL_DEBUG,
},
};
const struct util_options_defs fexhook_options_defs = {
.usage_header =
"Pumptools fexhook for Pump It Up: FiestaEX, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n"
"Usage: LD_PRELOAD=./fexhook.so [game exec] [game data path] <options>",
.usage_param = 'h',
.defs = fexhook_options_def,
.ndefs = lengthof(fexhook_options_def)
};
bool fexhook_options_init(int argc, char** argv, struct fexhook_options* options)
{
log_assert(argv);
log_assert(options);
struct util_options_opts* options_opt;
util_options_init(argc, argv);
options_opt = util_options_get(&fexhook_options_defs);
if (!options_opt) {
return false;
}
options->game.settings = util_options_get_str(options_opt, FEXHOOK_OPTIONS_STR_GAME_SETTINGS);
options->patch.gfx.windowed = util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED);
options->patch.hook_mon.file = util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE);
options->patch.hook_mon.fs = util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS);
options->patch.hook_mon.io = util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO);
options->patch.hook_mon.open = util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN);
options->patch.hook_mon.usb = util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB);
options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt,
FEXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS);
options->patch.main_loop.x11_input_handler_api_lib =
util_options_get_str(options_opt, FEXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER);
options->patch.net.server = util_options_get_str(options_opt, FEXHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER);
options->patch.net.machine_id = strtoul(
util_options_get_str(options_opt, FEXHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID),
NULL,
16);
options->patch.piuio.api_lib = util_options_get_str(options_opt, FEXHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB);
options->patch.piuio.exit_test_serv =
util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV);
options->patch.sound.device = util_options_get_str(options_opt, FEXHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE);
options->patch.sigsegv.halt_on_segv =
util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV);
options->log.file = util_options_get_str(options_opt, FEXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE);
options->log.level =
(enum util_log_level) util_options_get_int(options_opt, FEXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL);
return true;
}
+59
View File
@@ -0,0 +1,59 @@
#ifndef FEXHOOK_OPTIONS_H
#define FEXHOOK_OPTIONS_H
#include <stdbool.h>
#include <stdint.h>
#include "util/log.h"
struct fexhook_options {
struct game {
const char* settings;
} game;
struct patch {
struct gfx {
bool windowed;
} gfx;
struct hook_mon {
bool file;
bool fs;
bool io;
bool open;
bool usb;
} hook_mon;
struct main_loop {
bool disable_built_in_inputs;
const char* x11_input_handler_api_lib;
} main_loop;
struct net {
const char* server;
uint32_t machine_id;
} net;
struct piuio {
const char* api_lib;
bool exit_test_serv;
} piuio;
struct sound {
const char* device;
} sound;
struct sigsegv {
bool halt_on_segv;
} sigsegv;
} patch;
struct log {
const char* file;
enum util_log_level level;
} log;
};
bool fexhook_options_init(int argc, char** argv, struct fexhook_options* options);
#endif
+1
View File
@@ -0,0 +1 @@
DUMMY NOT RELEASED YET
+1
View File
@@ -0,0 +1 @@
DUMMY NOT RELEASED YET
+242
View File
@@ -0,0 +1,242 @@
/**
* Hook library for Pump It Up: Fiesta
*/
#define LOG_MODULE "fsthook"
#include <string.h>
#include "capnhook/hook/lib-main.h"
#include "capnhook/hooklib/redir.h"
#include "hook/core/piu-utils.h"
#include "hook/patch/amixer-block.h"
#include "hook/patch/gfx.h"
#include "hook/patch/hdd-check.h"
#include "hook/patch/hook-mon.h"
#include "hook/patch/main-loop.h"
#include "hook/patch/microdog40.h"
#include "hook/patch/mounts.h"
#include "hook/patch/piuio.h"
#include "hook/patch/piuio-exit.h"
#include "hook/patch/redir.h"
#include "hook/patch/sigsegv.h"
#include "hook/patch/sound.h"
#include "hook/patch/usb-emu.h"
#include "hook/patch/usb-init-fix.h"
#include "hook/patch/usb-mnt.h"
#include "hook/patch/usbfs.h"
#include "util/fs.h"
#include "util/glibc.h"
#include "util/sys-info.h"
#include "options.h"
/* Compiled binary data from data folder. Symbols are defined by compiler */
extern const uint8_t _binary_fsthook_hdd_raw_start[];
extern const uint8_t _binary_fsthook_hdd_raw_end[];
extern const uint8_t _binary_fsthook_dog_key_start[];
extern const uint8_t _binary_fsthook_dog_key_end[];
static void fsthook_bootstrapping(int argc, char** argv, struct fsthook_options* options, char** game_data_path)
{
log_assert(argc >= 0);
log_assert(argv);
log_assert(options);
log_assert(game_data_path);
if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) {
return;
}
hook_core_piu_log_init(argc, argv);
if (!fsthook_options_init(argc, argv, options)) {
exit(0);
}
hook_core_piu_log_info(argc, argv);
*game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv);
hook_core_piu_utils_verify_root_user();
log_info("Initializing, piu game data path: %s", *game_data_path);
}
static void fsthook_patch_fs_init(struct fsthook_options* options)
{
log_assert(options);
patch_hook_mon_init(
options->patch.hook_mon.io,
options->patch.hook_mon.file,
options->patch.hook_mon.fs,
options->patch.hook_mon.usb,
options->patch.hook_mon.open);
patch_redir_init();
}
static void fsthook_fs_redir_settings(struct fsthook_options* options)
{
log_assert(options);
if (options->game.settings) {
if (!util_fs_path_exists(options->game.settings)) {
log_info("Settings path %s does not exist, creating", options->game.settings);
if (!util_fs_mkdir(options->game.settings)) {
log_error("Creating directory %s failed", options->game.settings);
}
}
char* abs_path = util_fs_get_abs_path(options->game.settings);
cnh_redir_add("/SETTINGS", abs_path);
free(abs_path);
} else {
log_warn("No settings path specified");
}
}
static void fsthook_fs_redir_usb()
{
/* Needed on newer kernels */
cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices");
patch_usbfs_init();
}
static void fsthook_fs_redirs_init(struct fsthook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
fsthook_fs_redir_settings(options);
fsthook_fs_redir_usb();
}
static void fsthook_patch_fs_mounting_init()
{
patch_mounts_init();
patch_usb_mnt_init();
}
static void fsthook_patch_gfx_init(struct fsthook_options* options)
{
log_assert(options);
patch_gfx_init();
if (options->patch.gfx.windowed) {
patch_gfx_force_window_mode();
}
}
static void fsthook_patch_main_loop_init(struct fsthook_options* options)
{
log_assert(options);
patch_main_loop_init(false, options->patch.main_loop.disable_built_in_inputs, false);
if (options->patch.main_loop.x11_input_handler_api_lib) {
char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib);
patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib);
free(abs_path_x11_input_hook_lib);
}
}
static void fsthook_patch_sound_init(struct fsthook_options* options)
{
log_assert(options);
patch_sound_init(options->patch.sound.device);
patch_axmier_block_init();
}
static void fsthook_patch_sigsegv_init(struct fsthook_options* options)
{
log_assert(options);
patch_sigsegv_init(options->patch.sigsegv.halt_on_segv);
}
static void fsthook_patch_dongle_init()
{
patch_microdog40_init((const uint8_t*) _binary_fsthook_dog_key_start,
((uintptr_t) &_binary_fsthook_dog_key_end - (uintptr_t) &_binary_fsthook_dog_key_start));
}
static void fsthook_patch_hdd_check_init()
{
patch_hdd_check_init((const uint8_t*) _binary_fsthook_hdd_raw_start,
((uintptr_t) &_binary_fsthook_hdd_raw_end - (uintptr_t) &_binary_fsthook_hdd_raw_start));
}
static void fsthook_patch_piuio_init(struct fsthook_options* options)
{
log_assert(options);
/* Hook before IO emulation */
if (options->patch.piuio.exit_test_serv) {
patch_piuio_exit_init();
}
patch_usb_init_fix_init();
patch_usb_emu_init();
if (options->patch.piuio.api_lib) {
char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib);
patch_piuio_init(abs_path_iolib);
free(abs_path_iolib);
}
}
void fsthook_constructor(void)
{
/* Nothing here */
}
void fsthook_destructor(void)
{
/* Nothing here */
}
void fsthook_trap_before_main(int argc, char** argv)
{
log_assert(argc >= 0);
log_assert(argv);
char* game_data_path;
struct fsthook_options options;
fsthook_bootstrapping(argc, argv, &options, &game_data_path);
fsthook_patch_fs_init(&options);
fsthook_fs_redirs_init(&options, game_data_path);
fsthook_patch_fs_mounting_init();
fsthook_patch_gfx_init(&options);
fsthook_patch_main_loop_init(&options);
fsthook_patch_sound_init(&options);
fsthook_patch_sigsegv_init(&options);
fsthook_patch_dongle_init();
fsthook_patch_hdd_check_init();
fsthook_patch_piuio_init(&options);
free(game_data_path);
log_info("Hooking finished");
}
void fsthook_trap_after_main(void)
{
patch_piuio_shutdown();
}
LIB_MAIN_CONSTRUCTOR(fsthook_constructor);
LIB_MAIN_DESTRUCTOR(fsthook_destructor);
LIB_MAIN_TRAP_MAIN(fsthook_trap_before_main, fsthook_trap_after_main);
+195
View File
@@ -0,0 +1,195 @@
#include "hook/fst/options.h"
#include "util/options.h"
#define FSTHOOK_OPTIONS_STR_GAME_SETTINGS "game.settings"
#define FSTHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed"
#define FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file"
#define FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs"
#define FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io"
#define FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open"
#define FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb"
#define FSTHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs"
#define FSTHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler"
#define FSTHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER "patch.net_profile.server"
#define FSTHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID "patch.net_profile.machine_id"
#define FSTHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib"
#define FSTHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv"
#define FSTHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device"
#define FSTHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv"
#define FSTHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file"
#define FSTHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level"
const struct util_options_def fsthook_options_def[] = {
{
.name = FSTHOOK_OPTIONS_STR_GAME_SETTINGS,
.description = "Path to game settings (SETTINGS) folder",
.param = 's',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "./save",
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED,
.description = "Force game into window mode",
.param = 'w',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE,
.description = "Enable file call monitoring",
.param = 'f',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS,
.description = "Enable file system related call monitoring",
.param = 'g',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO,
.description = "Enable IO syscall monitoring",
.param = 'i',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN,
.description = "Enable any file/IO open call monitoring",
.param = 'j',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB,
.description = "Enable libusb call monitoring",
.param = 'u',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS,
.description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)",
.param = 'k',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER,
.description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs",
.param = 'q',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER,
.description = "Address and port of pumpnet server (e.g. 127.0.0.1:1234)",
.param = 'n',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID,
.description = "Machine id for pumpnet",
.param = 'm',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "0",
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB,
.description = "Path to library implementing the piuio api for piuio emulation",
.param = 'p',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV,
.description = "Enable game exit on Test + Service",
.param = 'e',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE,
.description = "Select the sound device to open on snd_pcm_open",
.param = 'a',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "dmix",
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV,
.description = "Halt on sigsegv to attach a debugger to the process",
.param = 'd',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.str = false,
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE,
.description = "Print the log output to the specified file",
.param = 'o',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "pumptools.log",
},
{
.name = FSTHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL,
.description = "Set the log level (0-4)",
.param = 'l',
.type = UTIL_OPTIONS_TYPE_INT,
.default_value.i = LOG_LEVEL_DEBUG,
},
};
const struct util_options_defs fsthook_options_defs = {
.usage_header =
"Pumptools fsthook for Pump It Up: Fiesta, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n"
"Usage: LD_PRELOAD=./fsthook.so [game exec] [game data path] <options>",
.usage_param = 'h',
.defs = fsthook_options_def,
.ndefs = lengthof(fsthook_options_def)
};
bool fsthook_options_init(int argc, char** argv, struct fsthook_options* options)
{
log_assert(argv);
log_assert(options);
struct util_options_opts* options_opt;
util_options_init(argc, argv);
options_opt = util_options_get(&fsthook_options_defs);
if (!options_opt) {
return false;
}
options->game.settings = util_options_get_str(options_opt, FSTHOOK_OPTIONS_STR_GAME_SETTINGS);
options->patch.gfx.windowed = util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED);
options->patch.hook_mon.file = util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE);
options->patch.hook_mon.fs = util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS);
options->patch.hook_mon.io = util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO);
options->patch.hook_mon.open = util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN);
options->patch.hook_mon.usb = util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB);
options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt,
FSTHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS);
options->patch.main_loop.x11_input_handler_api_lib =
util_options_get_str(options_opt, FSTHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER);
options->patch.net.server = util_options_get_str(options_opt, FSTHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER);
options->patch.net.machine_id = strtoul(
util_options_get_str(options_opt, FSTHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID),
NULL,
16);
options->patch.piuio.api_lib = util_options_get_str(options_opt, FSTHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB);
options->patch.piuio.exit_test_serv =
util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV);
options->patch.sound.device = util_options_get_str(options_opt, FSTHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE);
options->patch.sigsegv.halt_on_segv =
util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV);
options->log.file = util_options_get_str(options_opt, FSTHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE);
options->log.level =
(enum util_log_level) util_options_get_int(options_opt, FSTHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL);
return true;
}
+59
View File
@@ -0,0 +1,59 @@
#ifndef FSTHOOK_OPTIONS_H
#define FSTHOOK_OPTIONS_H
#include <stdbool.h>
#include <stdint.h>
#include "util/log.h"
struct fsthook_options {
struct game {
const char* settings;
} game;
struct patch {
struct gfx {
bool windowed;
} gfx;
struct hook_mon {
bool file;
bool fs;
bool io;
bool open;
bool usb;
} hook_mon;
struct main_loop {
bool disable_built_in_inputs;
const char* x11_input_handler_api_lib;
} main_loop;
struct net {
const char* server;
uint32_t machine_id;
} net;
struct piuio {
const char* api_lib;
bool exit_test_serv;
} piuio;
struct sound {
const char* device;
} sound;
struct sigsegv {
bool halt_on_segv;
} sigsegv;
} patch;
struct log {
const char* file;
enum util_log_level level;
} log;
};
bool fsthook_options_init(int argc, char** argv, struct fsthook_options* options);
#endif
+41
View File
@@ -0,0 +1,41 @@
#define LOG_MODULE "mk3hook-1st-2nd-fs-fix"
#include <string.h>
#include "capnhook/hook/lib.h"
#include "util/log.h"
#include "util/str.h"
typedef int (*cnh_fshook_chdir_t)(const char *path);
static cnh_fshook_chdir_t mk3hook_1st_2nd_fs_fix_real_chdir;
static char* mk3hook_1st_2nd_fs_fix_path_game_dir_abs;
int chdir(const char *path)
{
if (!mk3hook_1st_2nd_fs_fix_real_chdir) {
mk3hook_1st_2nd_fs_fix_real_chdir = (cnh_fshook_chdir_t) cnh_lib_get_func_addr("chdir");
}
// This seems to be some kind of probe the game is doing to determine something...I assume which folder layout to
// use? When we change directory to our "game" dir which contains all stuff at that point, everything's fine.
// If we do not do that, the game will error not being able to find any files. When loading files, the code also
// does some odd iterating the file system starting at / at that point. I assume that this is some sort of search
// trying to look for the file on the filesystem. However, that breaks a lot of other stuf with the hooking code
// and therefore should not be triggered.
if (mk3hook_1st_2nd_fs_fix_path_game_dir_abs && !strcmp(path, "/datas/bga")) {
log_debug("Trapping /datas/bga chdir, chdir to %s", mk3hook_1st_2nd_fs_fix_path_game_dir_abs);
return mk3hook_1st_2nd_fs_fix_real_chdir(mk3hook_1st_2nd_fs_fix_path_game_dir_abs);
} else {
return mk3hook_1st_2nd_fs_fix_real_chdir(path);
}
}
void mk3hook_1st_2nd_fs_fix_init(const char* path_game_dir_abs)
{
mk3hook_1st_2nd_fs_fix_path_game_dir_abs = util_str_dup(path_game_dir_abs);
log_info("Initialized");
}
+14
View File
@@ -0,0 +1,14 @@
/**
* Fixes an issue with the engine of 1st and 2nd when redirecting all game data to one game folder, e.g. ./game/.
*/
#ifndef MK3HOOK_1ST_2ND_FS_FIX_H
#define MK3HOOK_1ST_2ND_FS_FIX_H
/**
* Initialize the module.
*
* @param path_game_dir_abs The absolute path to the "game" dir containing all game assets.
*/
void mk3hook_1st_2nd_fs_fix_init(const char* path_game_dir_abs);
#endif
+136
View File
@@ -0,0 +1,136 @@
#define LOG_MODULE "mk3hook-config"
#include <libconfig.h>
#include "hook/mk3/options.h"
#include "util/fs.h"
#include "util/log.h"
#include "util/str.h"
static int mk3hook_config_sync_offset;
static double mk3hook_config_sync_multiplier;
static double mk3hook_config_music_volume;
static double mk3hook_config_sfx_volume;
static int mk3hook_config_fullscreen;
static int mk3hook_config_allow_exit;
static const char* mk3hook_config_save_file;
int config_read_file(config_t *config, const char *filename)
{
// Just stub file loading
log_debug("config_read_file: %s", filename);
return 1;
}
int config_lookup_int(const config_t *config, const char *path, int *value)
{
log_debug("config_lookup_int: %s", path);
if (!strcmp(path, "sync_offset")) {
*value = mk3hook_config_sync_offset;
} else {
log_error("Unsupported int entry read: %s", path);
}
return 1;
}
int config_lookup_float(const config_t *config, const char *path, double *value)
{
log_debug("config_lookup_float: %s", path);
if (!strcmp(path, "sync_multiplier")) {
*value = mk3hook_config_sync_multiplier;
} else if (!strcmp(path, "music_volume")) {
*value = mk3hook_config_music_volume;
} else if (!strcmp(path, "sfx_volume")) {
*value = mk3hook_config_sfx_volume;
} else {
log_error("Unsupported float entry read: %s", path);
}
return 1;
}
int config_lookup_bool(const config_t *config, const char *path, int *value)
{
log_debug("config_lookup_bool: %s", path);
if (!strcmp(path, "fullscreen")) {
*value = mk3hook_config_fullscreen;
} else if (!strcmp(path, "allow_exit")) {
*value = mk3hook_config_allow_exit;
} else {
log_error("Unsupported bool entry read: %s", path);
}
return 1;
}
int config_lookup_string(const config_t *config, const char *path, const char **value)
{
log_debug("config_lookup_string: %s", path);
if (!strcmp(path, "save_file")) {
// Note: This is fine as the size of the string pointed to by the returned address is not relevant for any
// operations using them, e.g. there is no merging/copying with limited stack'd buffer sizes involved like
// below.
*value = mk3hook_config_save_file;
} else if (!strcmp(path, "effect_path")) {
// Needs to be hardcoded because the stack buffer for this is only 128 bytes large. This is likely fine for most
// paths and even when resolving relative paths to absolute ones. Still, this is a risk for a buffer overflow.
// Utilize the redirect module in the main lib hook function to redirect this to something like a local game dir
// and resolve this to an absolute path
*value = "EFFECTS/";
} else if (!strcmp(path, "track_path")) {
// Needs to be hardcoded because the stack buffer for this is only 32 bytes large. When combining it with the
// "Track%02i.mp3" part, the buffer will overflow when this path is longer or even resolved to an absolute path.
// Utilize the redirect module in the main lib hook function to redirect this to something like a local game dir
// and resolve this to an absolute path
*value = "track/";
} else {
log_error("Unsupported string entry read: %s", path);
}
return 1;
}
static void mk3hook_config_assign_save_file(const struct mk3hook_options* options)
{
const char* folder = options->game.settings;
if (!util_fs_path_exists(folder)) {
log_info("Settings path %s does not exist, creating", folder);
if (!util_fs_mkdir(folder)) {
log_error("Creating directory %s failed", folder);
}
}
char* folder_abs = util_fs_get_abs_path(folder);
if (!folder_abs) {
log_error("Failed to resolve absolute path for settings folder");
return;
}
// Don't free this one after assigning the result value as this will still be read from. Keep memory leak.
mk3hook_config_save_file = util_str_merge(folder_abs, "/EEPROM.BIN");
free(folder_abs);
}
void mk3hook_config_init(const struct mk3hook_options* options)
{
mk3hook_config_sync_offset = options->game.sync_offset;
mk3hook_config_sync_multiplier = options->game.sync_multiplier;
mk3hook_config_music_volume = options->game.music_vol;
mk3hook_config_sfx_volume = options->game.sfx_vol;
mk3hook_config_fullscreen = !options->patch.gfx.windowed;
mk3hook_config_allow_exit = options->patch.piuio.exit_test_serv;
mk3hook_config_assign_save_file(options);
log_info("Initialized");
}
+17
View File
@@ -0,0 +1,17 @@
/**
* Patch the configuration file handling which uses libconfig. This hooks libconfig up to pumptools configuration
* infrastructure.
*/
#ifndef MK3HOOK_CONFIG_H
#define MK3HOOK_CONFIG_H
#include "options.h"
/**
* Initialize this module.
*
* @param options The options provided to the hook which are injected into libconfig config value reading.
*/
void mk3hook_config_init(const struct mk3hook_options* options);
#endif
+126
View File
@@ -0,0 +1,126 @@
#define LOG_MODULE "mk3hook-fmodex"
#include <fmodex/fmod.h>
#include <fmodex/fmod_errors.h>
#include "capnhook/hook/lib.h"
#include "hook/mk3/fmodex.h"
#include "util/log.h"
#include "util/str.h"
typedef FMOD_RESULT (*FMOD_System_Init_t)(FMOD_SYSTEM *system, int maxchannels, FMOD_INITFLAGS flags, void *extradriverdata);
static FMOD_System_Init_t mk3hook_fmodex_real_FMOD_System_Init;
static bool mk3hook_fmodex_debug_output;
static const char* mk3hook_fmodex_sound_device;
FMOD_RESULT FMOD_System_Init(FMOD_SYSTEM *system, int maxchannels, FMOD_INITFLAGS flags, void *extradriverdata)
{
if (!mk3hook_fmodex_real_FMOD_System_Init) {
mk3hook_fmodex_real_FMOD_System_Init = (FMOD_System_Init_t) cnh_lib_get_func_addr("FMOD_System_Init");
}
FMOD_RESULT res;
unsigned int version;
res = FMOD_System_GetVersion(system, &version);
if (res != FMOD_OK) {
log_error("FMOD_System_GetVersion failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res));
return res;
}
log_info("fmodex version: %d.%d.%d", (version >> 16) & 0xFFFF, (version >> 8) & 0xFF, version & 0xFF);
if (mk3hook_fmodex_debug_output) {
res = FMOD_Debug_SetLevel(FMOD_DEBUG_ALL);
if (res != FMOD_OK) {
log_error("FMOD_Debug_SetLevel failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res));
return res;
}
}
res = FMOD_System_SetOutput(system, FMOD_OUTPUTTYPE_ALSA);
if (res != FMOD_OK) {
log_error("FMOD_System_SetOutput failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res));
return res;
}
FMOD_OUTPUTTYPE output_type;
res = FMOD_System_GetOutput(system, &output_type);
if (res != FMOD_OK) {
log_error("FMOD_System_GetOutput failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res));
return res;
}
log_info("Output type: %d", output_type);
int num_drivers;
res = FMOD_System_GetNumDrivers(system, &num_drivers);
if (res != FMOD_OK) {
log_error("FMOD_System_GetNumDrivers failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res));
return res;
}
log_info("Num available drivers %d", num_drivers);
char buffer[4096];
FMOD_GUID guid;
int selected_driver = -1;
for (int i = 0; i < num_drivers; i++) {
res = FMOD_System_GetDriverInfo(system, i, buffer, sizeof(buffer), &guid);
if (res != FMOD_OK) {
log_error("FMOD_System_GetDriverInfo failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res));
break;
}
log_info("Driver %d: %s", i, buffer);
if (mk3hook_fmodex_sound_device && !strcmp(buffer, mk3hook_fmodex_sound_device)) {
selected_driver = i;
}
}
if (selected_driver == -1) {
log_info("Selecting default driver (0) because either the selected driver was not found or no driver was "
"selected");
selected_driver = 0;
} else {
log_info("Found selected driver %s, id %d", mk3hook_fmodex_sound_device, selected_driver);
}
res = FMOD_System_SetDriver(system, selected_driver);
if (res != FMOD_OK) {
log_error("FMOD_System_SetDriver failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res));
return res;
}
res = FMOD_System_SetSpeakerMode(system, FMOD_SPEAKERMODE_STEREO);
if (res != FMOD_OK) {
log_error("FMOD_System_SetSpeakerMode failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res));
return res;
}
return mk3hook_fmodex_real_FMOD_System_Init(system, maxchannels, flags, extradriverdata);
}
void mk3hook_fmodex_init(bool debug_output, const char* sound_device)
{
mk3hook_fmodex_debug_output = debug_output;
mk3hook_fmodex_sound_device = sound_device;
log_info("Initialized");
}
+19
View File
@@ -0,0 +1,19 @@
/**
* This patch module fixes a bunch of issues with fmodex which is used by the MK3 Linux ports to re-implement
* the sound hardware in software.
*/
#ifndef MK3HOOK_FMODEX_H
#define MK3HOOK_FMODEX_H
#include <stdbool.h>
/**
* Initialize this patch module.
*
* @param debug_output True to enable fmodex's debug output which requires the usage of fmodexL.so instead of fmodex.so.
* @param sound_device Provide a sound device to pick from the list of devices available with fmodex. NULL to go with
* with default device selected automatically by fmodex.
*/
void mk3hook_fmodex_init(bool debug_output, const char* sound_device);
#endif
+251
View File
@@ -0,0 +1,251 @@
/**
* Hook library for MK3 linux ports of Pump It Up.
*/
#define LOG_MODULE "mk3hook"
#include <string.h>
#include "capnhook/hook/lib-main.h"
#include "capnhook/hooklib/redir.h"
#include "hook/core/piu-utils.h"
#include "hook/mk3/1st-2nd-fs-fix.h"
#include "hook/mk3/config.h"
#include "hook/mk3/fmodex.h"
#include "hook/mk3/options.h"
#include "hook/patch/asound-fix.h"
#include "hook/patch/hook-mon.h"
#include "hook/patch/main-loop.h"
#include "hook/patch/piuio.h"
#include "hook/patch/piuio-exit.h"
#include "hook/patch/redir.h"
#include "hook/patch/sigsegv.h"
#include "hook/patch/sound.h"
#include "hook/patch/usb-emu.h"
#include "util/fs.h"
#include "util/glibc.h"
#include "util/log.h"
#include "util/patch.h"
#include "util/proc.h"
#include "util/str.h"
#include "util/sys-info.h"
static void mk3hook_bootstrapping(int argc, char** argv, struct mk3hook_options* options, char** game_data_path)
{
log_assert(argc >= 0);
log_assert(argv);
log_assert(options);
log_assert(game_data_path);
if (!mk3hook_options_init(argc, argv, options)) {
exit(0);
}
hook_core_piu_log_init(argc, argv);
hook_core_piu_log_info(argc, argv);
// Have different arguments to align with newer pump games
// argv[1]: game folder path
*game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv);
hook_core_piu_utils_verify_root_user();
log_info("Initializing, piu game data path: %s", *game_data_path);
}
static void mk3hook_patch_config_init(struct mk3hook_options* options)
{
mk3hook_config_init(options);
}
static void mk3hook_patch_fs_init(struct mk3hook_options* options)
{
log_assert(options);
patch_hook_mon_init(
options->patch.hook_mon.io,
options->patch.hook_mon.file,
options->patch.hook_mon.fs,
options->patch.hook_mon.usb,
options->patch.hook_mon.open);
patch_redir_init();
}
static void mk3hook_1st_2nd_fs_redirs(const char* game_data_path_abs)
{
log_assert(game_data_path_abs);
// The "datas" directory does not require a redirect as the game uses chdir to move to that folder. For this, the
// additional patch module "1st-2nd-fs-fix" is required (see below). All files usually local to the piu executable,
// which are outside of the "datas" folder, have to be redirected.
cnh_redir_add("INTRO.ANI", util_str_merge(game_data_path_abs, "/INTRO.ANI"));
cnh_redir_add("intro.ani", util_str_merge(game_data_path_abs, "/INTRO.ANI"));
cnh_redir_add("spr.res", util_str_merge(game_data_path_abs, "/SPR.RES"));
cnh_redir_add("SPR.RES", util_str_merge(game_data_path_abs, "/SPR.RES"));
cnh_redir_add("piu.bin", util_str_merge(game_data_path_abs, "/PIU.BIN"));
cnh_redir_add("PIU.BIN", util_str_merge(game_data_path_abs, "/PIU.BIN"));
cnh_redir_add("stage.cfg", util_str_merge(game_data_path_abs, "/STAGE.CFG"));
cnh_redir_add("STAGE.CFG", util_str_merge(game_data_path_abs, "/STAGE.CFG"));
// For redirecting additional files for linux ports. Configurable via MK3 linux ports config.cfg but causes stack
// overflow on long paths. Redirecting the paths afterwards is save.
cnh_redir_add("EFFECTS", util_str_merge(game_data_path_abs, "/EFFECTS/"));
cnh_redir_add("track", util_str_merge(game_data_path_abs, "/track/"));
mk3hook_1st_2nd_fs_fix_init(game_data_path_abs);
}
static void mk3hook_3rd_and_up_fs_redirs(const char* game_data_path_abs)
{
log_assert(game_data_path_abs);
// Sometimes, the game decides to read stuff using lower case, sometimes upper case *shrug*
// Also, we don't are about cleaning up the resulting merged strings here. Have some memory leaks!
cnh_redir_add(".", util_str_merge(game_data_path_abs, "/"));
cnh_redir_add("audio", util_str_merge(game_data_path_abs, "/AUDIO"));
cnh_redir_add("AUDIO", util_str_merge(game_data_path_abs, "/AUDIO"));
cnh_redir_add("bga", util_str_merge(game_data_path_abs, "/BGA"));
cnh_redir_add("BGA", util_str_merge(game_data_path_abs, "/BGA"));
cnh_redir_add("step", util_str_merge(game_data_path_abs, "/STEP"));
cnh_redir_add("STEP", util_str_merge(game_data_path_abs, "/STEP"));
cnh_redir_add("title", util_str_merge(game_data_path_abs, "/TITLE"));
cnh_redir_add("TITLE", util_str_merge(game_data_path_abs, "/TITLE"));
cnh_redir_add("piu.bin", util_str_merge(game_data_path_abs, "/PIU.BIN"));
cnh_redir_add("PIU.BIN", util_str_merge(game_data_path_abs, "/PIU.BIN"));
cnh_redir_add("stage.cfg", util_str_merge(game_data_path_abs, "/STAGE.CFG"));
cnh_redir_add("STAGE.CFG", util_str_merge(game_data_path_abs, "/STAGE.CFG"));
// Special treatment for extra because keeping the name stage.cfg was not good enough...
cnh_redir_add("game.cfg", util_str_merge(game_data_path_abs, "/GAME.CFG"));
cnh_redir_add("GAME.CFG", util_str_merge(game_data_path_abs, "/GAME.CFG"));
// For redirecting additional files for linux ports. Configurable via MK3 linux ports config.cfg but causes stack
// overflow on long paths. Redirecting the paths afterwards is save.
cnh_redir_add("EFFECTS", util_str_merge(game_data_path_abs, "/EFFECTS/"));
cnh_redir_add("track", util_str_merge(game_data_path_abs, "/track/"));
}
static void mk3hook_patch_1st_and_2nd_fs_init(struct mk3hook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
char* game_data_path_abs = util_fs_get_abs_path(game_data_path);
if (options->game.fs_1st_2nd) {
mk3hook_1st_2nd_fs_redirs(game_data_path_abs);
} else {
mk3hook_3rd_and_up_fs_redirs(game_data_path_abs);
}
free(game_data_path_abs);
}
static void mk3hook_patch_main_loop_init(struct mk3hook_options* options)
{
log_assert(options);
patch_main_loop_init(true, options->patch.main_loop.disable_built_in_inputs, true);
if (options->patch.main_loop.x11_input_handler_api_lib) {
char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib);
patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib);
free(abs_path_x11_input_hook_lib);
}
}
static void mk3hook_patch_sound_init(struct mk3hook_options* options)
{
log_assert(options);
patch_asound_fix_init();
mk3hook_fmodex_init(options->patch.sound.debug_output, options->patch.sound.device);
}
static void mk3hook_patch_sigsegv_init(struct mk3hook_options* options)
{
log_assert(options);
patch_sigsegv_init(options->patch.sigsegv.halt_on_segv);
}
static void mk3hook_patch_piuio_init(struct mk3hook_options* options)
{
log_assert(options);
/* Hook before IO emulation */
if (options->patch.piuio.exit_test_serv) {
patch_piuio_exit_init();
}
patch_usb_emu_init();
if (options->patch.piuio.api_lib) {
char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib);
patch_piuio_init(abs_path_iolib);
free(abs_path_iolib);
}
}
static void mk3hook_fix_argv(int argc, char** argv)
{
log_assert(argc >= 0);
log_assert(argv);
// Game exec main expects the following arguments
// argv[1]: cfg1 path
// argv[2]: cfg2 path (optional)
// Config loading is stubbed, so just provide something
argv[1] = "./save/config.cfg";
if (argc > 2) {
argv[2] = "./save/config.cfg";
}
}
void mk3hook_constructor(void)
{
/* Nothing here */
}
void mk3hook_destructor(void)
{
/* Nothing here */
}
void mk3hook_trap_before_main(int argc, char** argv)
{
log_assert(argc >= 0);
log_assert(argv);
struct mk3hook_options options;
char* game_data_path;
mk3hook_bootstrapping(argc, argv, &options, &game_data_path);
mk3hook_patch_config_init(&options);
mk3hook_patch_fs_init(&options);
mk3hook_patch_1st_and_2nd_fs_init(&options, game_data_path);
mk3hook_patch_main_loop_init(&options);
mk3hook_patch_sound_init(&options);
mk3hook_patch_sigsegv_init(&options);
mk3hook_patch_piuio_init(&options);
mk3hook_fix_argv(argc, argv);
free(game_data_path);
log_info("Hooking finished");
}
void mk3hook_trap_after_main(void)
{
}
LIB_MAIN_CONSTRUCTOR(mk3hook_constructor);
LIB_MAIN_DESTRUCTOR(mk3hook_destructor);
LIB_MAIN_TRAP_MAIN(mk3hook_trap_before_main, mk3hook_trap_after_main);
+37
View File
@@ -0,0 +1,37 @@
/**
* Module to fix weird OpenGL error that only appears on all MK3 linux ports.
*
* If OpenGL is not forced into single threaded mode, the backend will run in
* multi-threaded mode for some reason and crash in __glDispatchCheckMultithreaded
* when the game calls glXChooseVisual in its GFX init function.
*
* The fix is quite simple (luckily) by just setting the environment variable __GL_SINGLETHREADED.
*/
#include <stdio.h>
#include <string.h>
#include "capnhook/hook/lib.h"
typedef char* (*getenv_t) (const char* name);
static getenv_t _mk3hook_ogl_fix_real_getenv;
char* getenv(const char* name)
{
if (!_mk3hook_ogl_fix_real_getenv) {
_mk3hook_ogl_fix_real_getenv = (getenv_t) cnh_lib_get_func_addr("getenv");
}
char* ret = _mk3hook_ogl_fix_real_getenv(name);
if (!strcmp(name, "__GL_SINGLETHREADED")) {
if (ret == NULL || strcmp(name, "1") != 0) {
printf("!!! mk3hook: __GL_SINGLETHREADED not set or enabled, force patching OpenGL to single "
"threaded to avoid crash !!!\n");
return "1";
}
}
return ret;
}
+230
View File
@@ -0,0 +1,230 @@
#include "hook/mk3/options.h"
#include "util/options.h"
#define MK3HOOK_OPTIONS_STR_GAME_SETTINGS "game.settings"
#define MK3HOOK_OPTIONS_STR_GAME_SYNC_OFFSET "game.sync_offset"
#define MK3HOOK_OPTIONS_STR_GAME_SYNC_MULTIPLIER "game.sync_multiplier"
#define MK3HOOK_OPTIONS_STR_GAME_MUSIC_VOL "game.music_vol"
#define MK3HOOK_OPTIONS_STR_GAME_SFX_VOL "game.sfx_vol"
#define MK3HOOK_OPTIONS_STR_GAME_1ST_2ND_FS "game.1st_2nd_fs"
#define MK3HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed"
#define MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file"
#define MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs"
#define MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io"
#define MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open"
#define MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb"
#define MK3HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs"
#define MK3HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler"
#define MK3HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib"
#define MK3HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv"
#define MK3HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device"
#define MK3HOOK_OPTIONS_STR_PATCH_SOUND_DEBUG_OUTPUT "patch.sound.debug_output"
#define MK3HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv"
#define MK3HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file"
#define MK3HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level"
static const struct util_options_def mk3hook_options_def[] = {
{
.name = MK3HOOK_OPTIONS_STR_GAME_SETTINGS,
.description = "Path to game settings (SETTINGS) folder",
.param = 's',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "./save",
},
{
.name = MK3HOOK_OPTIONS_STR_GAME_SYNC_OFFSET,
.description = "Sync offset in ms",
.param = 'y',
.type = UTIL_OPTIONS_TYPE_INT,
.default_value.i = 115,
},
{
.name = MK3HOOK_OPTIONS_STR_GAME_SYNC_MULTIPLIER,
.description = "Sync multiplier",
.param = 'v',
.type = UTIL_OPTIONS_TYPE_DOUBLE,
.default_value.d = 4.16666666666666696272613990004,
},
{
.name = MK3HOOK_OPTIONS_STR_GAME_MUSIC_VOL,
.description = "Volume of music playback",
.param = 'm',
.type = UTIL_OPTIONS_TYPE_DOUBLE,
.default_value.d = 1.0,
},
{
.name = MK3HOOK_OPTIONS_STR_GAME_SFX_VOL,
.description = "Volume of sound effects playback",
.param = 'c',
.type = UTIL_OPTIONS_TYPE_DOUBLE,
.default_value.d = 1.0,
},
{
.name = MK3HOOK_OPTIONS_STR_GAME_1ST_2ND_FS,
.description = "Enable this option when running 1st or 2nd. This uses different file system redirects for the "
"game directory as well as enables a fix. Without this, the game throws errors about not finding files",
.param = 'x',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED,
.description = "Force game into window mode",
.param = 'w',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE,
.description = "Enable file call monitoring",
.param = 'f',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS,
.description = "Enable file system related call monitoring",
.param = 'g',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO,
.description = "Enable IO syscall monitoring",
.param = 'i',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN,
.description = "Enable any file/IO open call monitoring",
.param = 'j',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB,
.description = "Enable libusb call monitoring",
.param = 'u',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS,
.description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)",
.param = 'k',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER,
.description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs",
.param = 'q',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB,
.description = "Path to library implementing the piuio api for piuio emulation",
.param = 'p',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV,
.description = "Enable game exit on Test + Service",
.param = 'e',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE,
.description = "Select the sound device to open on snd_pcm_open",
.param = 'a',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_SOUND_DEBUG_OUTPUT,
.description = "Enable debug output for the fmod library. This requires you to use the fmodexL.so instead of "
"the standard fmodex.so",
.param = 'r',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV,
.description = "Halt on sigsegv to attach a debugger to the process",
.param = 'd',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.str = false,
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE,
.description = "Print the log output to the specified file",
.param = 'o',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "pumptools.log",
},
{
.name = MK3HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL,
.description = "Set the log level (0-4)",
.param = 'l',
.type = UTIL_OPTIONS_TYPE_INT,
.default_value.i = LOG_LEVEL_DEBUG,
},
};
static const struct util_options_defs mk3hook_options_defs = {
.usage_header =
"Pumptools mk3hook for Pump It Up MK3 Linux ports, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n"
"Usage: LD_PRELOAD=./mk3hook.so [game exec] [game data path] <options>",
.usage_param = 'h',
.defs = mk3hook_options_def,
.ndefs = lengthof(mk3hook_options_def)
};
bool mk3hook_options_init(int argc, char** argv, struct mk3hook_options* options)
{
log_assert(argv);
log_assert(options);
struct util_options_opts* options_opt;
util_options_init(argc, argv);
options_opt = util_options_get(&mk3hook_options_defs);
if (!options_opt) {
return false;
}
options->game.settings = util_options_get_str(options_opt, MK3HOOK_OPTIONS_STR_GAME_SETTINGS);
options->game.sync_offset = util_options_get_int(options_opt, MK3HOOK_OPTIONS_STR_GAME_SYNC_OFFSET);
options->game.sync_multiplier = util_options_get_double(options_opt, MK3HOOK_OPTIONS_STR_GAME_SYNC_MULTIPLIER);
options->game.music_vol = util_options_get_double(options_opt, MK3HOOK_OPTIONS_STR_GAME_MUSIC_VOL);
options->game.sfx_vol = util_options_get_double(options_opt, MK3HOOK_OPTIONS_STR_GAME_SFX_VOL);
options->game.fs_1st_2nd = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_GAME_1ST_2ND_FS);
options->patch.gfx.windowed = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED);
options->patch.hook_mon.file = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE);
options->patch.hook_mon.fs = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS);
options->patch.hook_mon.io = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO);
options->patch.hook_mon.open = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN);
options->patch.hook_mon.usb = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB);
options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt,
MK3HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS);
options->patch.main_loop.x11_input_handler_api_lib =
util_options_get_str(options_opt, MK3HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER);
options->patch.piuio.api_lib = util_options_get_str(options_opt, MK3HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB);
options->patch.piuio.exit_test_serv =
util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV);
options->patch.sound.device = util_options_get_str(options_opt, MK3HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE);
options->patch.sound.debug_output = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_SOUND_DEBUG_OUTPUT);
options->patch.sigsegv.halt_on_segv =
util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV);
options->log.file = util_options_get_str(options_opt, MK3HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE);
options->log.level =
(enum util_log_level) util_options_get_int(options_opt, MK3HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL);
return true;
}
+60
View File
@@ -0,0 +1,60 @@
#ifndef MK3HOOK_OPTIONS_H
#define MK3HOOK_OPTIONS_H
#include <stdbool.h>
#include <stdint.h>
#include "util/log.h"
struct mk3hook_options {
struct game {
const char* settings;
int32_t sync_offset;
double sync_multiplier;
double music_vol;
double sfx_vol;
bool fs_1st_2nd;
} game;
struct patch {
struct gfx {
bool windowed;
} gfx;
struct hook_mon {
bool file;
bool fs;
bool io;
bool open;
bool usb;
} hook_mon;
struct main_loop {
bool disable_built_in_inputs;
const char* x11_input_handler_api_lib;
} main_loop;
struct piuio {
const char* api_lib;
bool exit_test_serv;
} piuio;
struct sound {
const char* device;
bool debug_output;
} sound;
struct sigsegv {
bool halt_on_segv;
} sigsegv;
} patch;
struct log {
const char* file;
enum util_log_level level;
} log;
};
bool mk3hook_options_init(int argc, char** argv, struct mk3hook_options* options);
#endif
+272
View File
@@ -0,0 +1,272 @@
/**
* Hook library for Pump It Up: NX
*/
#define LOG_MODULE "nxhook"
#include <string.h>
#include "capnhook/hook/lib-main.h"
#include "capnhook/hooklib/redir.h"
#include "hook/core/piu-utils.h"
#include "hook/patch/gfx.h"
#include "hook/patch/hdd-check.h"
#include "hook/patch/hook-mon.h"
#include "hook/patch/main-loop.h"
#include "hook/patch/microdog34.h"
#include "hook/patch/piuio.h"
#include "hook/patch/piuio-exit.h"
#include "hook/patch/redir.h"
#include "hook/patch/sigsegv.h"
#include "hook/patch/sound.h"
#include "hook/patch/usb-emu.h"
#include "hook/patch/usb-init-fix.h"
#include "util/fs.h"
#include "util/glibc.h"
#include "util/str.h"
#include "util/sys-info.h"
#include "options.h"
#include "unlock.h"
/* Compiled binary data from data folder. Symbols are defined by compiler */
extern const uint8_t _binary_nxhook_hdd_raw_start[];
extern const uint8_t _binary_nxhook_hdd_raw_end[];
extern const uint8_t _binary_nxhook_dog_key_start[];
extern const uint8_t _binary_nxhook_dog_key_end[];
static void nxhook_bootstrapping(int argc, char** argv, struct nxhook_options* options, char** game_data_path)
{
log_assert(argc >= 0);
log_assert(argv);
log_assert(options);
log_assert(game_data_path);
if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) {
return;
}
hook_core_piu_log_init(argc, argv);
if (!nxhook_options_init(argc, argv, options)) {
exit(0);
}
hook_core_piu_log_info(argc, argv);
*game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv);
hook_core_piu_utils_verify_root_user();
log_info("Initializing, piu game data path: %s", *game_data_path);
}
static void nxhook_patch_fs_init(struct nxhook_options* options)
{
log_assert(options);
patch_hook_mon_init(
options->patch.hook_mon.io,
options->patch.hook_mon.file,
options->patch.hook_mon.fs,
options->patch.hook_mon.usb,
options->patch.hook_mon.open);
patch_redir_init();
}
static void nxhook_fs_redir_settings(struct nxhook_options* options)
{
log_assert(options);
if (options->game.settings) {
if (!util_fs_path_exists(options->game.settings)) {
log_info("Settings path %s does not exist, creating", options->game.settings);
if (!util_fs_mkdir(options->game.settings)) {
log_error("Creating directory %s failed", options->game.settings);
}
}
char* abs_path = util_fs_get_abs_path(options->game.settings);
cnh_redir_add("/SETTINGS", abs_path);
free(abs_path);
} else {
log_warn("No settings path specified");
}
}
static void nxhook_fs_redir_script(struct nxhook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
char* path_tmp;
path_tmp = util_str_merge(game_data_path, "/SCRIPT");
cnh_redir_add("/SCRIPT", path_tmp);
free(path_tmp);
}
static void nxhook_fs_redir_fonts(struct nxhook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
char* path_tmp;
path_tmp = util_str_merge(game_data_path, "/MICROGBE.TTF");
cnh_redir_add("MICROGBE.TTF", path_tmp);
free(path_tmp);
path_tmp = util_str_merge(game_data_path, "/NXTW.TTF");
cnh_redir_add("NXTW.TTF", path_tmp);
free(path_tmp);
}
static void nxhook_fs_redir_usb()
{
/* Needed on newer kernels */
cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices");
}
static void nxhook_fs_redirs_init(struct nxhook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
/* Expecting all assets in the piu game (asset) folder. redirects for files
that are not in there by default for reasons */
nxhook_fs_redir_settings(options);
nxhook_fs_redir_script(options, game_data_path);
nxhook_fs_redir_fonts(options, game_data_path);
nxhook_fs_redir_usb();
}
static void nxhook_patch_gfx_init(struct nxhook_options* options)
{
log_assert(options);
patch_gfx_init();
if (options->patch.gfx.windowed) {
patch_gfx_force_window_mode();
}
}
static void nxhook_patch_game_init(struct nxhook_options* options)
{
log_assert(options);
if (options->game.force_unlock) {
hook_nx_unlock_init();
}
}
static void nxhook_patch_main_loop_init(struct nxhook_options* options)
{
log_assert(options);
patch_main_loop_init(true, options->patch.main_loop.disable_built_in_inputs, false);
if (options->patch.main_loop.x11_input_handler_api_lib) {
char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib);
patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib);
free(abs_path_x11_input_hook_lib);
}
}
static void nxhook_patch_sound_init(struct nxhook_options* options)
{
log_assert(options);
patch_sound_init(options->patch.sound.device);
}
static void nxhook_patch_sigsegv_init(struct nxhook_options* options)
{
log_assert(options);
patch_sigsegv_init(options->patch.sigsegv.halt_on_segv);
}
static void nxhook_patch_dongle_init()
{
patch_microdog34_init((const uint8_t*) _binary_nxhook_dog_key_start,
((uintptr_t) &_binary_nxhook_dog_key_end - (uintptr_t) &_binary_nxhook_dog_key_start));
}
static void nxhook_patch_hdd_check_init()
{
patch_hdd_check_init((const uint8_t*) _binary_nxhook_hdd_raw_start,
((uintptr_t) &_binary_nxhook_hdd_raw_end - (uintptr_t) &_binary_nxhook_hdd_raw_start));
}
static void nxhook_patch_piuio_init(struct nxhook_options* options)
{
log_assert(options);
/* Hook before IO emulation */
if (options->patch.piuio.exit_test_serv) {
patch_piuio_exit_init();
}
patch_usb_init_fix_init();
patch_usb_emu_init();
if (options->patch.piuio.api_lib) {
char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib);
patch_piuio_init(abs_path_iolib);
free(abs_path_iolib);
}
}
void nxhook_constructor(void)
{
/* Nothing here */
}
void nxhook_destructor(void)
{
/* Nothing here */
}
void nxhook_trap_before_main(int argc, char** argv)
{
log_assert(argc >= 0);
log_assert(argv);
char* game_data_path;
struct nxhook_options options;
nxhook_bootstrapping(argc, argv, &options, &game_data_path);
nxhook_patch_fs_init(&options);
nxhook_fs_redirs_init(&options, game_data_path);
nxhook_patch_gfx_init(&options);
nxhook_patch_game_init(&options);
nxhook_patch_main_loop_init(&options);
nxhook_patch_sound_init(&options);
nxhook_patch_sigsegv_init(&options);
nxhook_patch_dongle_init();
nxhook_patch_hdd_check_init();
nxhook_patch_piuio_init(&options);
free(game_data_path);
log_info("Hooking finished");
}
void nxhook_trap_after_main(void)
{
patch_piuio_shutdown();
}
LIB_MAIN_CONSTRUCTOR(nxhook_constructor);
LIB_MAIN_DESTRUCTOR(nxhook_destructor);
LIB_MAIN_TRAP_MAIN(nxhook_trap_before_main, nxhook_trap_after_main);
Binary file not shown.
BIN
View File
Binary file not shown.
+183
View File
@@ -0,0 +1,183 @@
#include "hook/nx/options.h"
#include "util/options.h"
#define NXHOOK_OPTIONS_STR_GAME_FORCE_UNLOCK "game.force_unlock"
#define NXHOOK_OPTIONS_STR_GAME_SETTINGS "game.settings"
#define NXHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed"
#define NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file"
#define NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs"
#define NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io"
#define NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open"
#define NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb"
#define NXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs"
#define NXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler"
#define NXHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib"
#define NXHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv"
#define NXHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device"
#define NXHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv"
#define NXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file"
#define NXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level"
static const struct util_options_def nxhook_options_def[] = {
{
.name = NXHOOK_OPTIONS_STR_GAME_FORCE_UNLOCK,
.description = "Force unlock the game. Modifies the current and any new PIUNX.INI settings file permanently",
.param = 'z',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXHOOK_OPTIONS_STR_GAME_SETTINGS,
.description = "Path to game settings (SETTINGS) folder",
.param = 's',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "./save",
},
{
.name = NXHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED,
.description = "Force game into window mode",
.param = 'w',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE,
.description = "Enable file call monitoring",
.param = 'f',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS,
.description = "Enable file system related call monitoring",
.param = 'g',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO,
.description = "Enable IO syscall monitoring",
.param = 'i',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN,
.description = "Enable any file/IO open call monitoring",
.param = 'j',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB,
.description = "Enable libusb call monitoring",
.param = 'u',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS,
.description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)",
.param = 'k',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER,
.description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs",
.param = 'q',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = NXHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB,
.description = "Path to library implementing the piuio api for piuio emulation",
.param = 'p',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = NXHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV,
.description = "Enable game exit on Test + Service",
.param = 'e',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE,
.description = "Select the sound device to open on snd_pcm_open",
.param = 'a',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "dmix",
},
{
.name = NXHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV,
.description = "Halt on sigsegv to attach a debugger to the process",
.param = 'd',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.str = false,
},
{
.name = NXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE,
.description = "Print the log output to the specified file",
.param = 'o',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "pumptools.log",
},
{
.name = NXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL,
.description = "Set the log level (0-4)",
.param = 'l',
.type = UTIL_OPTIONS_TYPE_INT,
.default_value.i = LOG_LEVEL_DEBUG,
},
};
static const struct util_options_defs nxhook_options_defs = {
.usage_header =
"Pumptools nxhook for Pump It Up: NX, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n"
"Usage: LD_PRELOAD=./nxhook.so [game exec] [game data path] <options>",
.usage_param = 'h',
.defs = nxhook_options_def,
.ndefs = lengthof(nxhook_options_def)
};
bool nxhook_options_init(int argc, char** argv, struct nxhook_options* options)
{
log_assert(argv);
log_assert(options);
struct util_options_opts* options_opt;
util_options_init(argc, argv);
options_opt = util_options_get(&nxhook_options_defs);
if (!options_opt) {
return false;
}
options->game.force_unlock = util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_GAME_FORCE_UNLOCK);
options->game.settings = util_options_get_str(options_opt, NXHOOK_OPTIONS_STR_GAME_SETTINGS);
options->patch.gfx.windowed = util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED);
options->patch.hook_mon.file = util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE);
options->patch.hook_mon.fs = util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS);
options->patch.hook_mon.io = util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO);
options->patch.hook_mon.open = util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN);
options->patch.hook_mon.usb = util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB);
options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt,
NXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS);
options->patch.main_loop.x11_input_handler_api_lib =
util_options_get_str(options_opt, NXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER);
options->patch.piuio.api_lib = util_options_get_str(options_opt, NXHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB);
options->patch.piuio.exit_test_serv =
util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV);
options->patch.sound.device = util_options_get_str(options_opt, NXHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE);
options->patch.sigsegv.halt_on_segv =
util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV);
options->log.file = util_options_get_str(options_opt, NXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE);
options->log.level =
(enum util_log_level) util_options_get_int(options_opt, NXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL);
return true;
}
+55
View File
@@ -0,0 +1,55 @@
#ifndef NXHOOK_OPTIONS_H
#define NXHOOK_OPTIONS_H
#include <stdbool.h>
#include <stdint.h>
#include "util/log.h"
struct nxhook_options {
struct game {
bool force_unlock;
const char* settings;
} game;
struct patch {
struct gfx {
bool windowed;
} gfx;
struct hook_mon {
bool file;
bool fs;
bool io;
bool open;
bool usb;
} hook_mon;
struct main_loop {
bool disable_built_in_inputs;
const char* x11_input_handler_api_lib;
} main_loop;
struct piuio {
const char* api_lib;
bool exit_test_serv;
} piuio;
struct sound {
const char* device;
} sound;
struct sigsegv {
bool halt_on_segv;
} sigsegv;
} patch;
struct log {
const char* file;
enum util_log_level level;
} log;
};
bool nxhook_options_init(int argc, char** argv, struct nxhook_options* options);
#endif
+81
View File
@@ -0,0 +1,81 @@
#define LOG_MODULE "hook-nx-unlock"
#include <assert.h>
#include <string.h>
#include "asset/nx/lib/settings.h"
#include "capnhook/hook/filehook.h"
#include "util/log.h"
#include "util/str.h"
static FILE* _hook_nx_unlock_settings_file = NULL;
static enum cnh_result _hook_nx_unlock_filehook(struct cnh_filehook_irp *irp);
void hook_nx_unlock_init(void)
{
cnh_filehook_push_handler(_hook_nx_unlock_filehook);
log_info("Initialized");
}
static enum cnh_result _hook_nx_unlock_filehook(struct cnh_filehook_irp *irp)
{
enum cnh_result result;
assert(irp);
if (irp->op != CNH_FILEHOOK_IRP_OP_OPEN && irp->file != _hook_nx_unlock_settings_file) {
return cnh_filehook_invoke_next(irp);
}
switch (irp->op) {
case CNH_FILEHOOK_IRP_OP_OPEN:
{
/* Let it open the real file */
result = cnh_filehook_invoke_next(irp);
if (util_str_ends_with(irp->open_filename, ASSET_NX_SETTINGS_FILE_NAME)) {
if (result == CNH_RESULT_SUCCESS) {
log_debug("Intercepting settings file open");
_hook_nx_unlock_settings_file = irp->file;
}
}
break;
}
case CNH_FILEHOOK_IRP_OP_READ:
{
/* Let it read the real file */
result = cnh_filehook_invoke_next(irp);
if (result == CNH_RESULT_SUCCESS) {
struct asset_nx_settings* settings = (struct asset_nx_settings*) irp->read.bytes;
asset_nx_settings_unlock_all(settings);
log_debug("Applied unlock patch to loaded settings (buffer size %d)", irp->read.pos);
}
break;
}
case CNH_FILEHOOK_IRP_OP_CLOSE:
{
_hook_nx_unlock_settings_file = NULL;
result = cnh_filehook_invoke_next(irp);
break;
}
default:
result = cnh_filehook_invoke_next(irp);
break;
}
return result;
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Patch module to force unlock all songs/features of the game without modifying the configuration stored on disk.
*/
#ifndef HOOK_NX_UNLOCK_H
#define HOOK_NX_UNLOCK_H
/**
* Initialize the patch module force unlocking all locked game content.
*/
void hook_nx_unlock_init(void);
#endif
+320
View File
@@ -0,0 +1,320 @@
#define LOG_MODULE "nx2hook"
#include <string.h>
#include "capnhook/hook/lib-main.h"
#include "capnhook/hooklib/redir.h"
#include "hook/core/piu-utils.h"
#include "hook/patch/gfx.h"
#include "hook/patch/hdd-check.h"
#include "hook/patch/hook-mon.h"
#include "hook/patch/main-loop.h"
#include "hook/patch/microdog40.h"
#include "hook/patch/mounts.h"
#include "hook/patch/net-profile.h"
#include "hook/patch/piuio.h"
#include "hook/patch/piuio-exit.h"
#include "hook/patch/redir.h"
#include "hook/patch/sigsegv.h"
#include "hook/patch/sound.h"
#include "hook/patch/usb-emu.h"
#include "hook/patch/usb-init-fix.h"
#include "hook/patch/usb-mnt.h"
#include "hook/patch/usb-unlock.h"
#include "hook/patch/usbfs.h"
#include "util/fs.h"
#include "util/glibc.h"
#include "util/str.h"
#include "util/sys-info.h"
#include "options.h"
#include "profile-gen.h"
/* Compiled binary data from data folder. Symbols are defined by compiler */
extern const uint8_t _binary_nx2hook_hdd_raw_start[];
extern const uint8_t _binary_nx2hook_hdd_raw_end[];
extern const uint8_t _binary_nx2hook_dog_key_start[];
extern const uint8_t _binary_nx2hook_dog_key_end[];
static void nx2hook_bootstrapping(int argc, char** argv, struct nx2hook_options* options, char** game_data_path)
{
log_assert(argc >= 0);
log_assert(argv);
log_assert(options);
log_assert(game_data_path);
if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) {
return;
}
hook_core_piu_log_init(argc, argv);
if (!nx2hook_options_init(argc, argv, options)) {
exit(0);
}
hook_core_piu_log_info(argc, argv);
*game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv);
hook_core_piu_utils_verify_root_user();
log_info("Initializing, piu game data path: %s", *game_data_path);
}
static void nx2hook_patch_fs_init(struct nx2hook_options* options)
{
log_assert(options);
patch_hook_mon_init(
options->patch.hook_mon.io,
options->patch.hook_mon.file,
options->patch.hook_mon.fs,
options->patch.hook_mon.usb,
options->patch.hook_mon.open);
patch_redir_init();
}
static void nx2hook_fs_redir_settings(struct nx2hook_options* options)
{
log_assert(options);
if (options->game.settings) {
if (!util_fs_path_exists(options->game.settings)) {
log_info("Settings path %s does not exist, creating", options->game.settings);
if (!util_fs_mkdir(options->game.settings)) {
log_error("Creating directory %s failed", options->game.settings);
}
}
char* abs_path = util_fs_get_abs_path(options->game.settings);
cnh_redir_add("/SETTINGS", abs_path);
free(abs_path);
} else {
log_warn("No settings path specified");
}
}
static void nx2hook_fs_redir_script(struct nx2hook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
char* path_tmp;
path_tmp = util_str_merge(game_data_path, "/SCRIPT");
cnh_redir_add("/SCRIPT", path_tmp);
free(path_tmp);
}
static void nx2hook_fs_redir_fonts(struct nx2hook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
char* path_tmp;
/* Fonts, game expects lower case files */
path_tmp = util_str_merge(game_data_path, "/nx.ttf");
cnh_redir_add("nx.ttf", path_tmp);
free(path_tmp);
path_tmp = util_str_merge(game_data_path, "/nxcn.ttf");
cnh_redir_add("nxcn.ttf", path_tmp);
free(path_tmp);
path_tmp = util_str_merge(game_data_path, "/nxpt.ttf");
cnh_redir_add("nxpt.ttf", path_tmp);
free(path_tmp);
path_tmp = util_str_merge(game_data_path, "/nxtw.ttf");
cnh_redir_add("nxtw.ttf", path_tmp);
free(path_tmp);
}
static void nx2hook_fs_redir_mission(struct nx2hook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
char* path_tmp;
/* mission.txt, game expects lower case file */
path_tmp = util_str_merge(game_data_path, "/mission.txt");
cnh_redir_add("/mission.txt", path_tmp);
free(path_tmp);
}
static void nx2hook_fs_redir_usb()
{
/* Needed on newer kernels */
cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices");
patch_usbfs_init();
}
static void nx2hook_fs_redirs_init(struct nx2hook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
/* Expecting all assets in the piu game (asset) folder. redirects for files
that are not in there by default for reasons */
nx2hook_fs_redir_settings(options);
nx2hook_fs_redir_script(options, game_data_path);
nx2hook_fs_redir_fonts(options, game_data_path);
nx2hook_fs_redir_mission(options, game_data_path);
nx2hook_fs_redir_usb();
}
static void nx2hook_patch_fs_mounting_init()
{
patch_mounts_init();
patch_usb_mnt_init();
patch_usb_unlock_init();
}
static void nx2hook_patch_gfx_init(struct nx2hook_options* options)
{
log_assert(options);
patch_gfx_init();
if (options->patch.gfx.windowed) {
patch_gfx_force_window_mode();
}
}
static void nx2hook_patch_main_loop_init(struct nx2hook_options* options)
{
log_assert(options);
patch_main_loop_init(false, options->patch.main_loop.disable_built_in_inputs, false);
if (options->patch.main_loop.x11_input_handler_api_lib) {
char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib);
patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib);
free(abs_path_x11_input_hook_lib);
}
}
static void nx2hook_patch_sound_init(struct nx2hook_options* options)
{
log_assert(options);
patch_sound_init(options->patch.sound.device);
}
static void nx2hook_patch_sigsegv_init(struct nx2hook_options* options)
{
log_assert(options);
patch_sigsegv_init(options->patch.sigsegv.halt_on_segv);
}
static void nx2hook_patch_dongle_init()
{
patch_microdog40_init((const uint8_t*) _binary_nx2hook_dog_key_start,
((uintptr_t) &_binary_nx2hook_dog_key_end - (uintptr_t) &_binary_nx2hook_dog_key_start));
}
static void nx2hook_patch_hdd_check_init()
{
patch_hdd_check_init((const uint8_t*) _binary_nx2hook_hdd_raw_start,
((uintptr_t) &_binary_nx2hook_hdd_raw_end - (uintptr_t) &_binary_nx2hook_hdd_raw_start));
}
static void nx2hook_patch_piuio_init(struct nx2hook_options* options)
{
log_assert(options);
/* Hook before IO emulation */
if (options->patch.piuio.exit_test_serv) {
patch_piuio_exit_init();
}
patch_usb_init_fix_init();
patch_usb_emu_init();
if (options->patch.piuio.api_lib) {
char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib);
patch_piuio_init(abs_path_iolib);
free(abs_path_iolib);
}
}
static void nx2hook_patch_profile()
{
nx2hook_profile_gen_init();
}
static void nx2hook_patch_pumpnet(struct nx2hook_options* options)
{
if (options->patch.net.server && options->patch.net.machine_id != 0) {
patch_net_profile_init(
ASSET_GAME_VERSION_NX2,
options->patch.net.server,
options->patch.net.machine_id,
options->patch.net.cert_dir_path,
options->patch.net.verbose_log_output);
}
}
void nx2hook_constructor(void)
{
/* Nothing here */
}
void nx2hook_destructor(void)
{
/* Nothing here */
}
void nx2hook_trap_before_main(int argc, char** argv)
{
log_assert(argc >= 0);
log_assert(argv);
char* game_data_path;
struct nx2hook_options options;
nx2hook_bootstrapping(argc, argv, &options, &game_data_path);
nx2hook_patch_fs_init(&options);
nx2hook_fs_redirs_init(&options, game_data_path);
nx2hook_patch_fs_mounting_init();
nx2hook_patch_gfx_init(&options);
nx2hook_patch_main_loop_init(&options);
nx2hook_patch_sound_init(&options);
nx2hook_patch_sigsegv_init(&options);
nx2hook_patch_dongle_init();
nx2hook_patch_hdd_check_init();
nx2hook_patch_piuio_init(&options);
// Init order of pumpnet and auto profile generating important: pumpnet hook, if active, needs to be applied before
// the auto gen profile hook. Otherwise, the game detects that no profile is available and auto generates one even
// it should take the pumpnet route to grab a profile from a remote server
nx2hook_patch_pumpnet(&options);
nx2hook_patch_profile();
free(game_data_path);
log_info("Hooking finished");
}
void nx2hook_trap_after_main(void)
{
patch_net_profile_shutdown();
patch_piuio_shutdown();
}
LIB_MAIN_CONSTRUCTOR(nx2hook_constructor);
LIB_MAIN_DESTRUCTOR(nx2hook_destructor);
LIB_MAIN_TRAP_MAIN(nx2hook_trap_before_main, nx2hook_trap_after_main);
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+215
View File
@@ -0,0 +1,215 @@
#include "hook/nx2/options.h"
#include "util/options.h"
#define NX2HOOK_OPTIONS_STR_GAME_SETTINGS "game.settings"
#define NX2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed"
#define NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file"
#define NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs"
#define NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io"
#define NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open"
#define NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb"
#define NX2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs"
#define NX2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler"
#define NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER "patch.net_profile.server"
#define NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID "patch.net_profile.machine_id"
#define NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_VERBOSE_LOG_OUTPUT "patch.net_profile.verbose_log_output"
#define NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_CERT_DIR_PATH "patch.net_profile.cert_dir_path"
#define NX2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib"
#define NX2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv"
#define NX2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device"
#define NX2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv"
#define NX2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file"
#define NX2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level"
const struct util_options_def nx2hook_options_def[] = {
{
.name = NX2HOOK_OPTIONS_STR_GAME_SETTINGS,
.description = "Path to game settings (SETTINGS) folder",
.param = 's',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "./save",
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED,
.description = "Force game into window mode",
.param = 'w',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE,
.description = "Enable file call monitoring",
.param = 'f',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS,
.description = "Enable file system related call monitoring",
.param = 'g',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO,
.description = "Enable IO syscall monitoring",
.param = 'i',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN,
.description = "Enable any file/IO open call monitoring",
.param = 'j',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB,
.description = "Enable libusb call monitoring",
.param = 'u',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS,
.description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)",
.param = 'k',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER,
.description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs",
.param = 'q',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER,
.description = "Address and port of pumpnet server (e.g. 127.0.0.1:1234)",
.param = 'n',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID,
.description = "Machine id for pumpnet",
.param = 'm',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "0102030405060708",
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_VERBOSE_LOG_OUTPUT,
.description = "Enable verbose log output for pumpnet related things, e.g. logging network traffic",
.param = 'v',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_CERT_DIR_PATH,
.description = "Path to a folder containing the client key, certificate and CA bundle to enable https communication",
.param = 'c',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB,
.description = "Path to library implementing the piuio api for piuio emulation",
.param = 'p',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV,
.description = "Enable game exit on Test + Service",
.param = 'e',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE,
.description = "Select the sound device to open on snd_pcm_open",
.param = 'a',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "dmix",
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV,
.description = "Halt on sigsegv to attach a debugger to the process",
.param = 'd',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.str = false,
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE,
.description = "Print the log output to the specified file",
.param = 'o',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "pumptools.log",
},
{
.name = NX2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL,
.description = "Set the log level (0-4)",
.param = 'l',
.type = UTIL_OPTIONS_TYPE_INT,
.default_value.i = LOG_LEVEL_DEBUG,
},
};
const struct util_options_defs nx2hook_options_defs = {
.usage_header =
"Pumptools nx2hook for Pump It Up: NX2, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n"
"Usage: LD_PRELOAD=./nx2hook.so [game exec] [game data path] <options>",
.usage_param = 'h',
.defs = nx2hook_options_def,
.ndefs = lengthof(nx2hook_options_def)
};
bool nx2hook_options_init(int argc, char** argv, struct nx2hook_options* options)
{
log_assert(argv);
log_assert(options);
struct util_options_opts* options_opt;
util_options_init(argc, argv);
options_opt = util_options_get(&nx2hook_options_defs);
if (!options_opt) {
return false;
}
options->game.settings = util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_GAME_SETTINGS);
options->patch.gfx.windowed = util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED);
options->patch.hook_mon.file = util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE);
options->patch.hook_mon.fs = util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS);
options->patch.hook_mon.io = util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO);
options->patch.hook_mon.open = util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN);
options->patch.hook_mon.usb = util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB);
options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt,
NX2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS);
options->patch.main_loop.x11_input_handler_api_lib =
util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER);
options->patch.net.server = util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER);
options->patch.net.machine_id = strtoull(
util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID),
NULL,
16);
options->patch.net.verbose_log_output =
util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_VERBOSE_LOG_OUTPUT);
options->patch.net.cert_dir_path =
util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_CERT_DIR_PATH);
options->patch.piuio.api_lib = util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB);
options->patch.piuio.exit_test_serv =
util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV);
options->patch.sound.device = util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE);
options->patch.sigsegv.halt_on_segv =
util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV);
options->log.file = util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE);
options->log.level =
(enum util_log_level) util_options_get_int(options_opt, NX2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL);
return true;
}
+61
View File
@@ -0,0 +1,61 @@
#ifndef NX2HOOK_OPTIONS_H
#define NX2HOOK_OPTIONS_H
#include <stdbool.h>
#include <stdint.h>
#include "util/log.h"
struct nx2hook_options {
struct game {
const char* settings;
} game;
struct patch {
struct gfx {
bool windowed;
} gfx;
struct hook_mon {
bool file;
bool fs;
bool io;
bool open;
bool usb;
} hook_mon;
struct main_loop {
bool disable_built_in_inputs;
const char* x11_input_handler_api_lib;
} main_loop;
struct net {
const char* server;
uint64_t machine_id;
bool verbose_log_output;
const char* cert_dir_path;
} net;
struct piuio {
const char* api_lib;
bool exit_test_serv;
} piuio;
struct sound {
const char* device;
} sound;
struct sigsegv {
bool halt_on_segv;
} sigsegv;
} patch;
struct log {
const char* file;
enum util_log_level level;
} log;
};
bool nx2hook_options_init(int argc, char** argv, struct nx2hook_options* options);
#endif
+70
View File
@@ -0,0 +1,70 @@
#define LOG_MODULE "nx2hook-profile-gen"
#include <unistd.h>
#include "capnhook/hook/filehook.h"
#include "asset/nx2/lib/util.h"
#include "util/fs.h"
#include "util/log.h"
#include "util/str.h"
static bool nx2hook_profile_gen_is_creating_file;
// Do not use util_fs_path_exists here as this fopens and causes an endless recursion
static bool nx2hook_profile_gen_check_if_exists(const char* path)
{
return access(path, F_OK) != -1;
}
static enum cnh_result nx2hook_profile_gen_filehook(struct cnh_filehook_irp *irp)
{
// nx2hook_profile_gen_is_creating_file: Avoid recursion of any file calls when creating the save/rank files
if (irp->op != CNH_FILEHOOK_IRP_OP_OPEN || nx2hook_profile_gen_is_creating_file) {
return cnh_filehook_invoke_next(irp);
}
if (util_str_ends_with(irp->open_filename, "nx2save.bin") &&
!nx2hook_profile_gen_check_if_exists(irp->open_filename)) {
nx2hook_profile_gen_is_creating_file = true;
struct asset_nx2_usb_save* save = asset_nx2_usb_save_new();
asset_nx2_usb_save_finalize(save);
log_info("No %s file found, generate new", irp->open_filename);
if (!asset_nx2_usb_save_save_to_file(irp->open_filename, save, true)) {
log_error("Saving generated nx2save.bin file to %s failed", irp->open_filename);
}
free(save);
nx2hook_profile_gen_is_creating_file = false;
}
if (util_str_ends_with(irp->open_filename, "nx2rank.bin") &&
!nx2hook_profile_gen_check_if_exists(irp->open_filename)) {
nx2hook_profile_gen_is_creating_file = true;
struct asset_nx2_usb_rank* rank = asset_nx2_usb_rank_new();
asset_nx2_usb_rank_finalize(rank);
log_info("No %s file found, generate new", irp->open_filename);
if (!asset_nx2_usb_rank_save_to_file(irp->open_filename, rank, true)) {
log_error("Saving generated nx2rank.bin file to %s failed", irp->open_filename);
}
free(rank);
nx2hook_profile_gen_is_creating_file = false;
}
return cnh_filehook_invoke_next(irp);
}
void nx2hook_profile_gen_init(void)
{
cnh_filehook_push_handler(nx2hook_profile_gen_filehook);
}
+14
View File
@@ -0,0 +1,14 @@
/**
* Patch module for NX2 to enable auto generating of save files on empty/non
* Andamiro usb drives
*/
#ifndef NX2HOOK_PROFILE_GEN_H
#define NX2HOOK_PROFILE_GEN_H
/**
* Initialize this patch module to allow auto generation of new/empty profiles
* on usb sticks that do not contain stock NX2 profile data
*/
void nx2hook_profile_gen_init(void);
#endif
+310
View File
@@ -0,0 +1,310 @@
/**
* Hook library for Pump It Up: NXA
*/
#define LOG_MODULE "nxahook"
#include <string.h>
#include "capnhook/hook/lib-main.h"
#include "capnhook/hooklib/redir.h"
#include "hook/core/piu-utils.h"
#include "hook/patch/gfx.h"
#include "hook/patch/hdd-check.h"
#include "hook/patch/hook-mon.h"
#include "hook/patch/main-loop.h"
#include "hook/patch/microdog40.h"
#include "hook/patch/mounts.h"
#include "hook/patch/piuio.h"
#include "hook/patch/piuio-exit.h"
#include "hook/patch/redir.h"
#include "hook/patch/sigsegv.h"
#include "hook/patch/sound.h"
#include "hook/patch/usb-emu.h"
#include "hook/patch/usb-init-fix.h"
#include "hook/patch/usb-mnt.h"
#include "hook/patch/usb-unlock.h"
#include "hook/patch/usbfs.h"
#include "util/fs.h"
#include "util/glibc.h"
#include "util/str.h"
#include "util/sys-info.h"
#include "options.h"
#include "profile-gen.h"
/* Compiled binary data from data folder. Symbols are defined by compiler */
extern const uint8_t _binary_nxahook_hdd_raw_start[];
extern const uint8_t _binary_nxahook_hdd_raw_end[];
extern const uint8_t _binary_nxahook_dog_key_start[];
extern const uint8_t _binary_nxahook_dog_key_end[];
static void nxahook_bootstrapping(int argc, char** argv, struct nxahook_options* options, char** game_data_path)
{
log_assert(argc >= 0);
log_assert(argv);
log_assert(options);
log_assert(game_data_path);
if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) {
return;
}
hook_core_piu_log_init(argc, argv);
if (!nxahook_options_init(argc, argv, options)) {
exit(0);
}
hook_core_piu_log_info(argc, argv);
*game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv);
hook_core_piu_utils_verify_root_user();
log_info("Initializing, piu game data path: %s", *game_data_path);
}
static void nxahook_patch_fs_init(struct nxahook_options* options)
{
log_assert(options);
patch_hook_mon_init(
options->patch.hook_mon.io,
options->patch.hook_mon.file,
options->patch.hook_mon.fs,
options->patch.hook_mon.usb,
options->patch.hook_mon.open);
patch_redir_init();
}
static void nxahook_fs_redir_settings(struct nxahook_options* options)
{
log_assert(options);
if (options->game.settings) {
if (!util_fs_path_exists(options->game.settings)) {
log_info("Settings path %s does not exist, creating", options->game.settings);
if (!util_fs_mkdir(options->game.settings)) {
log_error("Creating directory %s failed", options->game.settings);
}
}
char* abs_path = util_fs_get_abs_path(options->game.settings);
cnh_redir_add("/SETTINGS", abs_path);
free(abs_path);
} else {
log_warn("No settings path specified");
}
}
static void nxahook_fs_redir_script(struct nxahook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
char* path_tmp;
path_tmp = util_str_merge(game_data_path, "/SCRIPT");
cnh_redir_add("/SCRIPT", path_tmp);
free(path_tmp);
}
static void nxahook_fs_redir_fonts(struct nxahook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
char* path_tmp;
/* Fonts, game expects lower case files */
path_tmp = util_str_merge(game_data_path, "/nx.ttf");
cnh_redir_add("nx.ttf", path_tmp);
free(path_tmp);
path_tmp = util_str_merge(game_data_path, "/nxcn.ttf");
cnh_redir_add("nxcn.ttf", path_tmp);
free(path_tmp);
path_tmp = util_str_merge(game_data_path, "/nxpt.ttf");
cnh_redir_add("nxpt.ttf", path_tmp);
free(path_tmp);
path_tmp = util_str_merge(game_data_path, "/nxtw.ttf");
cnh_redir_add("nxtw.ttf", path_tmp);
free(path_tmp);
}
static void nxahook_fs_redir_mission(struct nxahook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
char* path_tmp;
/* mission.txt, game expects lower case file */
path_tmp = util_str_merge(game_data_path, "/mission.txt");
cnh_redir_add("/mission.txt", path_tmp);
free(path_tmp);
/* ufo.txt, game expects lower case file */
path_tmp = util_str_merge(game_data_path, "/ufo.txt");
cnh_redir_add("/ufo.txt", path_tmp);
free(path_tmp);
}
static void nxahook_fs_redir_usb()
{
/* Needed on newer kernels */
cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices");
patch_usbfs_init();
}
static void nxahook_fs_redirs_init(struct nxahook_options* options, const char* game_data_path)
{
log_assert(options);
log_assert(game_data_path);
/* Expecting all assets in the piu game (asset) folder. redirects for files
that are not in there by default for reasons */
nxahook_fs_redir_settings(options);
nxahook_fs_redir_script(options, game_data_path);
nxahook_fs_redir_fonts(options, game_data_path);
nxahook_fs_redir_mission(options, game_data_path);
nxahook_fs_redir_usb();
}
static void nxahook_patch_fs_mounting_init()
{
patch_mounts_init();
patch_usb_mnt_init();
patch_usb_unlock_init();
}
static void nxahook_patch_gfx_init(struct nxahook_options* options)
{
log_assert(options);
patch_gfx_init();
if (options->patch.gfx.windowed) {
patch_gfx_force_window_mode();
}
}
static void nxahook_patch_main_loop_init(struct nxahook_options* options)
{
log_assert(options);
patch_main_loop_init(false, options->patch.main_loop.disable_built_in_inputs, false);
if (options->patch.main_loop.x11_input_handler_api_lib) {
char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib);
patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib);
free(abs_path_x11_input_hook_lib);
}
}
static void nxahook_patch_sound_init(struct nxahook_options* options)
{
log_assert(options);
patch_sound_init(options->patch.sound.device);
}
static void nxahook_patch_sigsegv_init(struct nxahook_options* options)
{
log_assert(options);
patch_sigsegv_init(options->patch.sigsegv.halt_on_segv);
}
static void nxahook_patch_dongle_init()
{
patch_microdog40_init((const uint8_t*) _binary_nxahook_dog_key_start,
((uintptr_t) &_binary_nxahook_dog_key_end - (uintptr_t) &_binary_nxahook_dog_key_start));
}
static void nxahook_patch_hdd_check_init()
{
patch_hdd_check_init((const uint8_t*) _binary_nxahook_hdd_raw_start,
((uintptr_t) &_binary_nxahook_hdd_raw_end - (uintptr_t) &_binary_nxahook_hdd_raw_start));
}
static void nxahook_patch_piuio_init(struct nxahook_options* options)
{
log_assert(options);
/* Hook before IO emulation */
if (options->patch.piuio.exit_test_serv) {
patch_piuio_exit_init();
}
patch_usb_init_fix_init();
patch_usb_emu_init();
if (options->patch.piuio.api_lib) {
char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib);
patch_piuio_init(abs_path_iolib);
free(abs_path_iolib);
}
}
static void nxahook_patch_profile()
{
nxahook_profile_gen_init();
}
void nxahook_constructor(void)
{
/* Nothing here */
}
void nxahook_destructor(void)
{
/* Nothing here */
}
void nxahook_trap_before_main(int argc, char** argv)
{
log_assert(argc >= 0);
log_assert(argv);
char* game_data_path;
struct nxahook_options options;
nxahook_bootstrapping(argc, argv, &options, &game_data_path);
nxahook_patch_fs_init(&options);
nxahook_fs_redirs_init(&options, game_data_path);
nxahook_patch_fs_mounting_init();
nxahook_patch_gfx_init(&options);
nxahook_patch_main_loop_init(&options);
nxahook_patch_sound_init(&options);
nxahook_patch_sigsegv_init(&options);
nxahook_patch_dongle_init();
nxahook_patch_hdd_check_init();
nxahook_patch_piuio_init(&options);
nxahook_patch_profile();
free(game_data_path);
log_info("Hooking finished");
}
void nxahook_trap_after_main(void)
{
patch_piuio_shutdown();
}
LIB_MAIN_CONSTRUCTOR(nxahook_constructor);
LIB_MAIN_DESTRUCTOR(nxahook_destructor);
LIB_MAIN_TRAP_MAIN(nxahook_trap_before_main, nxahook_trap_after_main);
+1
View File
@@ -0,0 +1 @@
DUMMY NOT RELEASED YET
+1
View File
@@ -0,0 +1 @@
DUMMY NOT RELEASED YET
+195
View File
@@ -0,0 +1,195 @@
#include "hook/nxa/options.h"
#include "util/options.h"
#define NXAHOOK_OPTIONS_STR_GAME_SETTINGS "game.settings"
#define NXAHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed"
#define NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file"
#define NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs"
#define NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io"
#define NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open"
#define NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb"
#define NXAHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs"
#define NXAHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler"
#define NXAHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER "patch.net_profile.server"
#define NXAHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID "patch.net_profile.machine_id"
#define NXAHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib"
#define NXAHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv"
#define NXAHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device"
#define NXAHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv"
#define NXAHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file"
#define NXAHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level"
const struct util_options_def nxahook_options_def[] = {
{
.name = NXAHOOK_OPTIONS_STR_GAME_SETTINGS,
.description = "Path to game settings (SETTINGS) folder",
.param = 's',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "./save",
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED,
.description = "Force game into window mode",
.param = 'w',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE,
.description = "Enable file call monitoring",
.param = 'f',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS,
.description = "Enable file system related call monitoring",
.param = 'g',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO,
.description = "Enable IO syscall monitoring",
.param = 'i',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN,
.description = "Enable any file/IO open call monitoring",
.param = 'j',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB,
.description = "Enable libusb call monitoring",
.param = 'u',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS,
.description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)",
.param = 'k',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER,
.description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs",
.param = 'q',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER,
.description = "Address and port of pumpnet server (e.g. 127.0.0.1:1234)",
.param = 'n',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID,
.description = "Machine id for pumpnet",
.param = 'm',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = 0,
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB,
.description = "Path to library implementing the piuio api for piuio emulation",
.param = 'p',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = NULL,
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV,
.description = "Enable game exit on Test + Service",
.param = 'e',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.b = false,
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE,
.description = "Select the sound device to open on snd_pcm_open",
.param = 'a',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "dmix",
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV,
.description = "Halt on sigsegv to attach a debugger to the process",
.param = 'd',
.type = UTIL_OPTIONS_TYPE_BOOL,
.default_value.str = false,
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE,
.description = "Print the log output to the specified file",
.param = 'o',
.type = UTIL_OPTIONS_TYPE_STR,
.default_value.str = "pumptools.log",
},
{
.name = NXAHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL,
.description = "Set the log level (0-4)",
.param = 'l',
.type = UTIL_OPTIONS_TYPE_INT,
.default_value.i = LOG_LEVEL_DEBUG,
},
};
const struct util_options_defs nxahook_options_defs = {
.usage_header =
"Pumptools nxahook for Pump It Up: NXA, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n"
"Usage: LD_PRELOAD=./nxahook.so [game exec] [game data path] <options>",
.usage_param = 'h',
.defs = nxahook_options_def,
.ndefs = lengthof(nxahook_options_def)
};
bool nxahook_options_init(int argc, char** argv, struct nxahook_options* options)
{
log_assert(argv);
log_assert(options);
struct util_options_opts* options_opt;
util_options_init(argc, argv);
options_opt = util_options_get(&nxahook_options_defs);
if (!options_opt) {
return false;
}
options->game.settings = util_options_get_str(options_opt, NXAHOOK_OPTIONS_STR_GAME_SETTINGS);
options->patch.gfx.windowed = util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED);
options->patch.hook_mon.file = util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE);
options->patch.hook_mon.fs = util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS);
options->patch.hook_mon.io = util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO);
options->patch.hook_mon.open = util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN);
options->patch.hook_mon.usb = util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB);
options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt,
NXAHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS);
options->patch.main_loop.x11_input_handler_api_lib =
util_options_get_str(options_opt, NXAHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER);
options->patch.net.server = util_options_get_str(options_opt, NXAHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER);
options->patch.net.machine_id = strtoul(
util_options_get_str(options_opt, NXAHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID),
NULL,
16);
options->patch.piuio.api_lib = util_options_get_str(options_opt, NXAHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB);
options->patch.piuio.exit_test_serv =
util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV);
options->patch.sound.device = util_options_get_str(options_opt, NXAHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE);
options->patch.sigsegv.halt_on_segv =
util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV);
options->log.file = util_options_get_str(options_opt, NXAHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE);
options->log.level =
(enum util_log_level) util_options_get_int(options_opt, NXAHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL);
return true;
}
+59
View File
@@ -0,0 +1,59 @@
#ifndef NXAHOOK_OPTIONS_H
#define NXAHOOK_OPTIONS_H
#include <stdbool.h>
#include <stdint.h>
#include "util/log.h"
struct nxahook_options {
struct game {
const char* settings;
} game;
struct patch {
struct gfx {
bool windowed;
} gfx;
struct hook_mon {
bool file;
bool fs;
bool io;
bool open;
bool usb;
} hook_mon;
struct main_loop {
bool disable_built_in_inputs;
const char* x11_input_handler_api_lib;
} main_loop;
struct net {
const char* server;
uint32_t machine_id;
} net;
struct piuio {
const char* api_lib;
bool exit_test_serv;
} piuio;
struct sound {
const char* device;
} sound;
struct sigsegv {
bool halt_on_segv;
} sigsegv;
} patch;
struct log {
const char* file;
enum util_log_level level;
} log;
};
bool nxahook_options_init(int argc, char** argv, struct nxahook_options* options);
#endif
+108
View File
@@ -0,0 +1,108 @@
#define LOG_MODULE "nxahook-profile-gen"
#include <unistd.h>
#include "capnhook/hook/filehook.h"
#include "asset/nxa/lib/util.h"
#include "util/fs.h"
#include "util/log.h"
#include "util/str.h"
static bool nxahook_profile_gen_is_creating_file;
// Do not use util_fs_path_exists here as this fopens and causes an endless recursion
static bool nxahook_profile_gen_check_if_exists(const char* path)
{
return access(path, F_OK) != -1;
}
static enum cnh_result nxahook_profile_gen_filehook(struct cnh_filehook_irp *irp)
{
// nx2hook_profile_gen_is_creating_file: Avoid recursion of any file calls when creating the save/rank files
if (irp->op != CNH_FILEHOOK_IRP_OP_OPEN || nxahook_profile_gen_is_creating_file) {
return cnh_filehook_invoke_next(irp);
}
if (util_str_ends_with(irp->open_filename, "nxasave.bin") &&
!nxahook_profile_gen_check_if_exists(irp->open_filename)) {
char* path_only;
char* path_nx2;
struct asset_nxa_usb_save* save;
nxahook_profile_gen_is_creating_file = true;
/* Check if a nx2save.bin exists and skip generating a new nxasave
file because the game migrates everything from the nx2 file */
path_only = util_fs_get_path_to_file(irp->open_filename);
path_nx2 = util_str_merge(path_only, "/nx2save.bin");
free(path_only);
if (util_fs_path_exists(path_nx2)) {
free(path_nx2);
log_info("nx2save.bin exists, don't create new nxasave.bin");
nxahook_profile_gen_is_creating_file = false;
return cnh_filehook_invoke_next(irp);
}
free(path_nx2);
save = asset_nxa_usb_save_new();
asset_nxa_usb_save_finalize(save);
log_info("No %s file found, generate new", irp->open_filename);
if (!asset_nxa_usb_save_save_to_file(irp->open_filename, save, true)) {
log_error("Saving generated nxasave.bin file to %s failed", irp->open_filename);
}
free(save);
nxahook_profile_gen_is_creating_file = false;
}
if (util_str_ends_with(irp->open_filename, "nxarank.bin") &&
!nxahook_profile_gen_check_if_exists(irp->open_filename)) {
char* path_only;
char* path_nx2;
struct asset_nxa_usb_rank* rank;
nxahook_profile_gen_is_creating_file = true;
/* Check if a nx2rank.bin exists and skip generating a new nxarank
file because the game migrates everything from the nx2 file */
path_only = util_fs_get_path_to_file(irp->open_filename);
path_nx2 = util_str_merge(path_only, "/nx2rank.bin");
free(path_only);
if (util_fs_path_exists(path_nx2)) {
free(path_nx2);
log_info("nx2rank.bin exists, don't create new nxarank.bin");
nxahook_profile_gen_is_creating_file = false;
return cnh_filehook_invoke_next(irp);
}
free(path_nx2);
rank = asset_nxa_usb_rank_new();
asset_nxa_usb_rank_finalize(rank);
log_info("No %s file found, generate new", irp->open_filename);
if (!asset_nxa_usb_rank_save_to_file(irp->open_filename, rank, true)) {
log_error("Saving generated nxarank.bin file to %s failed", irp->open_filename);
}
free(rank);
nxahook_profile_gen_is_creating_file = false;
}
return cnh_filehook_invoke_next(irp);
}
void nxahook_profile_gen_init(void)
{
cnh_filehook_push_handler(nxahook_profile_gen_filehook);
}
+14
View File
@@ -0,0 +1,14 @@
/**
* Patch module for NXA to enable auto generating of save files on empty/non
* Andamiro usb drives
*/
#ifndef NXAHOOK_PROFILE_GEN_H
#define NXAHOOK_PROFILE_GEN_H
/**
* Initialize this patch module to allow auto generation of new/empty profiles
* on usb sticks that do not contain stock NXA profile data
*/
void nxahook_profile_gen_init(void);
#endif
+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");
}

Some files were not shown because too many files have changed in this diff Show More