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