#!/usr/bin/env bash ### # Pump It Up Environment Bootstrap (piueb) # # This script sets up an environment to run all (MK5/6) Linux based PIU games as well as the MK3 linux ports. # It takes care of the following tasks: # - Check if all necessary files and directories are available # - Verify that local dependencies for the game are used # - Force the game into VSYNC # - Bootstrap the application executable with: # - A a subset of local libs or even a private ld-linux to ensure highest compatibility accross distros # - A pumptools hook.so # # Therefore, this script expects the following folder structure and naming (!) to run the game: # - game: The game folder with vanilla assets # - lib: All (additional) dependencies the game needs to run # - (save): Depending on your hook configuration, this can be located somewhere else, too # - hook.conf: Configuration file with hook specific settings # - hook.so: The game specific hook library compiled from pumptools # - piu: The game's executable # - piueb: This script # # To get usage/help output from piueb: # ./piueb help # # To run the game, simply run this script with the "run" command: # ./piueb run # # Additional arguments to hook.so are also supported: # ./piueb run arg1 arg2 # # For example, to get help/usage output from the hook library: # ./piueb run -h # # For debugging purpose, a log file (piueb.log) is written to the folder this script is in. ### readonly VERSION="1.0" readonly LOG_LEVEL_ERROR="E" readonly LOG_LEVEL_WARN="W" readonly LOG_LEVEL_INFO="I" readonly LOG_LEVEL_DEBUG="M" ################################################################################## SELF_DIR="$(dirname "$(readlink -f "$0")")" LOG_FILE="$SELF_DIR/piueb.log" GAME_EXEC="$SELF_DIR/piu" GAME_DIR="$SELF_DIR/game" LIBS_DIR="$SELF_DIR/lib" HOOK_FILE="$SELF_DIR/hook.so" HOOK_CONFIG_FILE="$SELF_DIR/hook.conf" LIBS_LINKER="$SELF_DIR/lib/ld-linux.so.2" ################################################################################## log_init() { rm -f "$LOG_FILE" } log() { local level="$1" local msg="$2" local timestamp="$(date "+%Y/%m/%d-%H:%M:%S:%3N")" local color="" case $level in $LOG_LEVEL_ERROR) color="\e[1m\e[31m" ;; $LOG_LEVEL_WARN) color="\e[33m" ;; $LOG_LEVEL_INFO) color="\e[34m" ;; $LOG_LEVEL_DEBUG) color="\e[37m" ;; esac echo -e "${color}[$level]\e[0m[$timestamp] ${msg}" echo "[$level][$timestamp] ${msg}" >> "$LOG_FILE" } log_error() { log "$LOG_LEVEL_ERROR" "$@" } log_warn() { log "$LOG_LEVEL_WARN" "$@" } log_info() { log "$LOG_LEVEL_INFO" "$@" } log_debug() { log "$LOG_LEVEL_DEBUG" "$@" } verify_running_as_root() { log_debug "Verify running as root user/with sudo..." if [ "$EUID" -ne 0 ]; then log_error "Running as non-root user, game likely to crash!" fi } verify_file_exists() { local file_path="$1" local warn_only="$2" log_debug "Verify file/dir exists $file_path..." if [ ! -e "$file_path" ]; then if [ "$warn_only" ]; then log_warn "Missing file \"$file_path\"" else log_error "Missing required file \"$file_path\"" exit 1 fi fi } verify_dir_not_empty() { local dir_path="$1" if [ -z "$(ls -A $dir_path)" ]; then log_error "Directory empty: $dir_path" exit 1 fi } verify_deps_all_local() { local exec_path="$1" local lib_path="$2" local ldd_output="$(ldd $exec_path >&1)" log_debug "Verify dependencies all local..." log_debug "ldd output for $exec_path:\\n$ldd_output" while read -r line; do # Skip any additional output/error messages that are not part of the library listing if [[ "$line:0:1" != "\t" ]]; then continue fi local libname="$(echo "$line" | cut -d '.' -f 1)" local libpath="$(echo "$line" | cut -d ' ' -f 3)" # Skip a bunch of libs that are kernel specific like bound to GPU drivers case $libname in "linux-gate") ;& # fallthrough "libGL") ;& # fallthrough "libGLX") ;& # fallthrough "libGLdispatch") ;& # fallthrough # That will still point to the current machine's linker # but we will use our own further down to bootstrap the elf "ld-linux") ;& # fallthrough "/lib/ld-linux") log_debug "Skipping $libname" continue ;; *) ;; esac log_debug "Checking $libname: $libpath..." if [[ $libpath != $LIBS_DIR* ]]; then log_warn "$libname is not local to the game, path $libpath" fi done <<< "$ldd_output" } verify_game_executable() { local exec_path="$1" log_debug "Verifying that game is executable: $exec_path..." if [ ! -x "$exec_path" ]; then log_warn "Game exec $exec_path not executable, fixing..." chmod +x "$exec_path" fi } print_usage() { echo "Pump It Up Environment Bootstrap (piueb), version $VERSION" echo "Easily bootstrap linux based Pump games using pumptools." echo "Usage: piueb ..." echo "Commands:" echo " run - Run the game located next to this script" echo " strace - Run the game with local libraries and strace attached for debugging" echo " debug - Run the game with local libraries and a GDB debugger server attached for debugging" echo " help - Print this usage message" } # Supports native mk6 linux games as well as the mk3 linux ports using mk3hook cmd_run() { local mode="$1" log_init log_info "Pump It Up Environment Bootstrap (piueb), version $VERSION" verify_running_as_root log_info "Current path: $SELF_DIR" log_debug "File and path verification..." # Some file and folder verfication verify_file_exists "$GAME_EXEC" verify_file_exists "$GAME_DIR" verify_dir_not_empty "$GAME_DIR" verify_file_exists "$LIBS_DIR" verify_dir_not_empty "$LIBS_DIR" verify_file_exists "$HOOK_FILE" # Only warn to allow creating of default hook.conf if no file exists verify_file_exists "$HOOK_CONFIG_FILE" 1 # Support two modes of setting up the runtime environment with the game's # dependencies: # 1. Using a separate linker application which might be necessary when having # to run the application with different libc (and/or other library) versions. # This avoids incompatibilities with newer libc runtime and ld-linux versions # (see f2-crashing-on-modern-linux-post-mortem.md). # # 2. Use only a subset of local libraries and take everything else from the # current system. Your typical LD_LIBRARY_PATH method. local lib_mode="" if [ -e "$LIBS_LINKER" ]; then lib_mode="ld" else lib_mode="local" fi log_info "Resolving dependencies mode: $lib_mode" # LD_LIBRARY_PATH setup to use local libraries instead of system ones if [ -z "$LD_LIBRARY_PATH" ]; then LD_LIBRARY_PATH="$LIBS_DIR" else LD_LIBRARY_PATH="$LIBS_DIR:$LD_LIBRARY_PATH" fi log_debug "LD_LIBRARY_PATH: $LD_LIBRARY_PATH" # Required that ldd shows us the correct paths export LD_LIBRARY_PATH # Check if all dependencies used are actually bundled with the game verify_deps_all_local "$GAME_EXEC" "$LIBS_DIR" local LD_PRELOAD="$HOOK_FILE" local ldd_output_hook="$(ldd $HOOK_FILE >&1)" log_debug "ldd output for $HOOK_FILE:\\n$ldd_output_hook" if [ "${*:1}" ]; then log_info "Additional cmd arguments provided: ${*:1}" fi log_info "Executing game $GAME_EXEC, mode $mode, lib_mode $lib_mode...\n==============================================================================================================================" # Because people forget this when setting up the data manually chmod +x "$GAME_EXEC" # Force the game to run to vsync blank interval for nvidia cards: __GL_SYNC_TO_VBLANK=1 if [ "$mode" = "normal" ] && [ "$lib_mode" = "ld" ]; then # / after GAME_DIR required # Use private linker to avoid platform incompatibility issues (see above) exec \ env __GL_SYNC_TO_VBLANK=1 \ env LD_PRELOAD="$LD_PRELOAD" \ "$LIBS_LINKER" \ --library-path "$LD_LIBRARY_PATH" \ "$GAME_EXEC" \ "${GAME_DIR}/" \ "--options" "$HOOK_CONFIG_FILE" \ "${@:2}" elif [ "$mode" = "normal" ] && [ "$lib_mode" = "local" ]; then # / after GAME_DIR required exec \ env __GL_SYNC_TO_VBLANK=1 \ env LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \ env LD_PRELOAD="$LD_PRELOAD" \ "$GAME_EXEC" \ "${GAME_DIR}/" \ "--options" "$HOOK_CONFIG_FILE" \ "${@:2}" elif [ "$mode" = "debug" ] && [ "$lib_mode" = "ld" ]; then log_error "debug mode with lib type ld not supported" elif [ "$mode" = "debug" ] && [ "$lib_mode" = "local" ]; then gdbserver \ --wrapper \ env \ __GL_SYNC_TO_VBLANK=1 \ LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \ LD_PRELOAD="$LD_PRELOAD" \ -- 0.0.0.0:1234 \ "$GAME_EXEC" \ "${GAME_DIR}/" \ "--options" "$HOOK_CONFIG_FILE" \ "${@:2}" elif [ "$mode" = "strace" ] && [ "$lib_mode" = "ld" ]; then log_error "strace mode with lib type ld not supported" elif [ "$mode" = "strace" ] && [ "$lib_mode" = "local" ]; then strace \ -E __GL_SYNC_TO_VBLANK=1 \ -E LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \ -E LD_PRELOAD="$LD_PRELOAD" \ "$GAME_EXEC" \ "${GAME_DIR}/" \ "--options" "$HOOK_CONFIG_FILE" \ "${@:2}" elif [ "$mode" = "valgrind" ] && [ "$lib_mode" = "ld" ]; then log_error "valgrind mode with lib type ld not supported" elif [ "$mode" = "valgrind" ] && [ "$lib_mode" = "local" ]; then valgrind \ --trace-children=yes \ env \ __GL_SYNC_TO_VBLANK=1 \ LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \ LD_PRELOAD="$LD_PRELOAD" \ "$GAME_EXEC" \ "${GAME_DIR}/" \ "--options" "$HOOK_CONFIG_FILE" \ "${@:2}" else log_error "Unsupported mode ($mode) or lib_mode (($lib_mode)" fi } cmd_help() { print_usage } ################################################################################## CMD="$1" case $CMD in "") echo "Insufficient arguments." print_usage exit 1 ;; "help") cmd_help exit 0 ;; "run") cmd_run "normal" "${@:2}" exit 0 ;; "debug") cmd_run "debug" "${@:2}" exit 0 ;; "strace") cmd_run "strace" "${@:2}" exit 0 ;; "valgrind") cmd_run "valgrind" "${@:2}" exit 0 ;; *) echo "Invalid command." print_usage exit 1 ;; esac