1
0
mirror of https://github.com/pumpitupdev/pumptools.git synced 2024-11-14 10:37:34 +01:00
pumptools/dist/piueb

407 lines
11 KiB
Bash
Executable File

#!/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 <cmd> ..."
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